koishi-plugin-chat-patch 5.3.1 → 5.5.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-Clqxn7CB.js +28 -0
- package/client/web/dist/assets/{Chat-C-rmzMIw.css → Chat-DTEsebnY.css} +1 -1
- package/client/web/dist/assets/{MsgBody-h4C1nnBQ.js → MsgBody-C0TKsXhC.js} +1 -1
- package/client/web/dist/assets/{MsgBody.vue_vue_type_script_setup_true_lang-a902ps2r.js → MsgBody.vue_vue_type_script_setup_true_lang-CCdBwlN_.js} +1 -1
- package/client/web/dist/assets/{append_darwin-CqBNU62J.css → append_darwin-Cql-dV6d.css} +1 -1
- package/client/web/dist/assets/{append_full_vibrancy-j0D2LM9m.css → append_full_vibrancy-ih3loy1O.css} +1 -1
- package/client/web/dist/assets/{append_linux-uhxeBqeC.css → append_linux-B3LvR8KB.css} +1 -1
- package/client/web/dist/assets/{append_new-407Lo5_V.css → append_new-DfhN_NX9.css} +1 -1
- package/client/web/dist/assets/{append_vibrancy-6FHctkOY.css → append_vibrancy-D3pVsmAa.css} +1 -1
- package/client/web/dist/assets/{append_win32-srmD-78y.css → append_win32-Dh9JlmlE.css} +1 -1
- package/client/web/dist/assets/index-B71pPwae.css +1 -0
- package/client/web/dist/assets/{index-CdTmWcO4.js → index-BA37EQGa.js} +96 -75
- package/client/web/dist/index.html +2 -2
- package/client/web/src/App.vue +2 -2
- package/client/web/src/assets/css/append/append_darwin.css +2 -2
- package/client/web/src/assets/css/append/append_full_vibrancy.css +2 -2
- package/client/web/src/assets/css/append/append_linux.css +2 -2
- package/client/web/src/assets/css/append/append_new.css +2 -2
- package/client/web/src/assets/css/append/append_vibrancy.css +1 -1
- package/client/web/src/assets/css/append/append_win32.css +2 -2
- package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +1 -1
- package/client/web/src/assets/css/chat.css +4 -3
- package/client/web/src/assets/css/msg.css +7 -3
- package/client/web/src/assets/css/options.css +2 -2
- package/client/web/src/assets/css/view.css +14 -3
- package/client/web/src/assets/l10n/zh-CN.po +21 -0
- package/client/web/src/components/AboutPan.vue +5 -3
- package/client/web/src/components/DepPan.vue +1 -1
- package/client/web/src/components/MsgBody.vue +0 -3
- package/client/web/src/components/ThemeColorPickerPan.vue +1 -1
- package/client/web/src/components/UmamiInfoPan.vue +1 -1
- package/client/web/src/components/WelPan.vue +1 -1
- package/client/web/src/function/connect.ts +232 -72
- package/client/web/src/function/elements/information.ts +1 -0
- package/client/web/src/function/msg.ts +42 -23
- package/client/web/src/function/satori-model.ts +27 -13
- package/client/web/src/function/satori.ts +2 -1
- package/client/web/src/function/utils/appUtil.ts +3 -11
- package/client/web/src/function/utils/localHistoryUtil.ts +36 -12
- package/client/web/src/function/utils/msgUtil.ts +32 -21
- package/client/web/src/function/utils/sessionUtil.ts +20 -18
- package/client/web/src/pages/Chat.vue +47 -43
- package/client/web/src/pages/Friends.vue +5 -7
- package/client/web/src/pages/Info.vue +2 -2
- package/client/web/src/pages/Messages.vue +37 -22
- package/client/web/src/pages/chat-view/ChatTerminal.vue +4 -1
- package/client/web/src/pages/options/OptDev.vue +45 -2
- package/lib/database.d.ts +58 -0
- package/lib/index.js +220 -68
- package/lib/media.d.ts +24 -0
- package/lib/recorder.d.ts +21 -0
- package/lib/self-message.d.ts +27 -0
- package/lib/types.d.ts +6 -0
- package/lib/web.d.ts +7 -0
- package/package.json +1 -1
- package/src/cache.ts +1 -5
- package/src/database.ts +52 -8
- package/src/index.ts +4 -4
- package/src/media.ts +97 -0
- package/src/recorder.ts +28 -19
- package/src/self-message.ts +68 -19
- package/src/types.ts +6 -0
- package/src/web.ts +49 -39
- package/client/web/dist/assets/Chat-Bo5JdcNg.js +0 -28
- package/client/web/dist/assets/index-wjp_A4n1.css +0 -1
|
@@ -21,9 +21,7 @@ function getString(value: unknown): string {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function normalizeGroupId(value: string): string {
|
|
24
|
-
|
|
25
|
-
const wrapped = raw.match(/^\[_?([\s\S]+?)_?\]$/)
|
|
26
|
-
return wrapped ? wrapped[1] : raw || value
|
|
24
|
+
return String(value)
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
function isDirectChannelType(channel: SatoriObject): boolean {
|
|
@@ -449,11 +447,11 @@ export function satoriEventToOneBot(
|
|
|
449
447
|
if (type === 'message' || type === 'message-created' || type === 'send') {
|
|
450
448
|
const isGroup = isGroupChannel(channel, guild)
|
|
451
449
|
const userId = getString(user.id)
|
|
452
|
-
|| (isGroup ? '' : getString(channel.id)
|
|
450
|
+
|| (isGroup ? '' : getString(channel.id))
|
|
453
451
|
const groupId = isGroup
|
|
454
452
|
? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
|
|
455
453
|
: ''
|
|
456
|
-
const directChannelId = isGroup ? groupId : userId
|
|
454
|
+
const directChannelId = isGroup ? groupId : getString(channel.id) || userId
|
|
457
455
|
const nickname = getString(user.name) || getString(user.nick) || userId
|
|
458
456
|
const card = getString(member.nick) || getString(member.name) || ''
|
|
459
457
|
return {
|
|
@@ -470,7 +468,7 @@ export function satoriEventToOneBot(
|
|
|
470
468
|
group_avatar: isGroup
|
|
471
469
|
? (usableAvatar(guild.avatar) || usableAvatar(channel.avatar) || buildQqGroupAvatar(platform, groupId) || undefined)
|
|
472
470
|
: undefined,
|
|
473
|
-
channel_id: getString(channel.id) || (isGroup ? groupId :
|
|
471
|
+
channel_id: getString(channel.id) || (isGroup ? groupId : userId),
|
|
474
472
|
guild_id: getString(guild.id),
|
|
475
473
|
target_id: selfId,
|
|
476
474
|
message: messageSegments(message),
|
|
@@ -486,7 +484,7 @@ export function satoriEventToOneBot(
|
|
|
486
484
|
message_id: getString(message.id),
|
|
487
485
|
private_id: isGroup ? '' : directChannelId,
|
|
488
486
|
group_id: groupId,
|
|
489
|
-
channel_id: getString(channel.id) || (isGroup ? groupId :
|
|
487
|
+
channel_id: getString(channel.id) || (isGroup ? groupId : userId),
|
|
490
488
|
guild_id: getString(guild.id),
|
|
491
489
|
target_id: selfId,
|
|
492
490
|
sender: userId,
|
|
@@ -533,6 +531,24 @@ export function satoriEventToOneBot(
|
|
|
533
531
|
}
|
|
534
532
|
}
|
|
535
533
|
|
|
534
|
+
if (type === 'message-deleted') {
|
|
535
|
+
const isGroup = isGroupChannel(channel, guild)
|
|
536
|
+
const userId = getString(user.id) || (isGroup ? '' : getString(channel.id))
|
|
537
|
+
const groupId = isGroup
|
|
538
|
+
? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
|
|
539
|
+
: ''
|
|
540
|
+
return {
|
|
541
|
+
...base,
|
|
542
|
+
post_type: 'notice',
|
|
543
|
+
notice_type: isGroup ? 'group_recall' : 'friend_recall',
|
|
544
|
+
group_id: groupId,
|
|
545
|
+
user_id: userId,
|
|
546
|
+
message_id: getString(message.id),
|
|
547
|
+
channel_id: getString(channel.id) || (isGroup ? groupId : userId),
|
|
548
|
+
guild_id: getString(guild.id),
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
536
552
|
return null
|
|
537
553
|
}
|
|
538
554
|
|
|
@@ -637,9 +653,7 @@ export function mapAction(action: string, params: Record<string, unknown>): {
|
|
|
637
653
|
const guildId = getString(params.group_id) || getString(params.guild_id)
|
|
638
654
|
const userId = getString(params.user_id)
|
|
639
655
|
const messageId = getString(params.message_id)
|
|
640
|
-
const directChannel = userId
|
|
641
|
-
? `private:${userId}`
|
|
642
|
-
: userId
|
|
656
|
+
const directChannel = userId
|
|
643
657
|
const channelId = getString(params.channel_id) || guildId || directChannel || userId
|
|
644
658
|
const content = Array.isArray(params.message)
|
|
645
659
|
? serializeSatoriContent(params.message)
|
|
@@ -696,8 +710,8 @@ function messageListFromResponse(data: unknown): unknown[] {
|
|
|
696
710
|
? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
|
|
697
711
|
: ''
|
|
698
712
|
const userId = getString(user.id)
|
|
699
|
-
|| (isGroup ? '' : getString(channel.id)
|
|
700
|
-
const directChannelId = isGroup ? groupId : userId
|
|
713
|
+
|| (isGroup ? '' : getString(channel.id))
|
|
714
|
+
const directChannelId = isGroup ? groupId : getString(channel.id) || userId
|
|
701
715
|
return {
|
|
702
716
|
message_id: getString(message.id),
|
|
703
717
|
sn: getNumber(message.sn),
|
|
@@ -709,7 +723,7 @@ function messageListFromResponse(data: unknown): unknown[] {
|
|
|
709
723
|
channel_type: channel.type,
|
|
710
724
|
group_id: groupId,
|
|
711
725
|
user_id: groupId ? userId : directChannelId,
|
|
712
|
-
channel_id: getString(channel.id) || (isGroup ? groupId :
|
|
726
|
+
channel_id: getString(channel.id) || (isGroup ? groupId : userId),
|
|
713
727
|
guild_id: getString(guild.id),
|
|
714
728
|
sender: {
|
|
715
729
|
user_id: userId,
|
|
@@ -75,7 +75,8 @@ async function parseJsonResponse(response: Response): Promise<unknown> {
|
|
|
75
75
|
try {
|
|
76
76
|
return JSON.parse(text) as unknown
|
|
77
77
|
} catch {
|
|
78
|
-
|
|
78
|
+
// 部分平台对 delete 等操作只回显纯文本(例如 OK),HTTP 200 即视为成功
|
|
79
|
+
return text
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -53,11 +53,6 @@ function encodeKeyPart(value: unknown): string {
|
|
|
53
53
|
return encodeURIComponent(String(value ?? ''))
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function ensureChannelPrefix(channelId: string, prefix: string): string {
|
|
57
|
-
if (/^(?:group|room|chat|channel|guild|private):/i.test(channelId)) return channelId
|
|
58
|
-
return `${prefix}:${channelId}`
|
|
59
|
-
}
|
|
60
|
-
|
|
61
56
|
/**
|
|
62
57
|
* 滚动到目标消息(不自动加载)
|
|
63
58
|
* @param seqName DOM 名(chat-xx)
|
|
@@ -121,7 +116,8 @@ export async function loadHistory(info: BaseChatInfoElem) {
|
|
|
121
116
|
uiStore.nowGetHistory = false
|
|
122
117
|
uiStore.historyBeforeTime = undefined
|
|
123
118
|
chatStore.messageList = []
|
|
124
|
-
const
|
|
119
|
+
const channelId = String(info.channel_id ?? info.id ?? '')
|
|
120
|
+
const cacheKey = [authStore.loginInfo.platform, authStore.loginInfo.uin, channelId].map(encodeKeyPart).join(':')
|
|
125
121
|
const cachedMessages = chatStore.sessionMessageCache.get(cacheKey)
|
|
126
122
|
if (cachedMessages?.length) {
|
|
127
123
|
chatStore.messageList = [...cachedMessages]
|
|
@@ -133,17 +129,13 @@ export async function loadHistory(info: BaseChatInfoElem) {
|
|
|
133
129
|
) {
|
|
134
130
|
const localMsgs = await dbGetLatest(
|
|
135
131
|
authStore.loginInfo.uin,
|
|
136
|
-
|
|
132
|
+
channelId,
|
|
137
133
|
20,
|
|
138
134
|
)
|
|
139
135
|
if (localMsgs.length > 0) {
|
|
140
136
|
chatStore.messageList = localMsgs
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
|
-
const rawChannelId = String(info.channel_id ?? '')
|
|
144
|
-
const channelId = info.type === 'group'
|
|
145
|
-
? ensureChannelPrefix(rawChannelId || String(info.id), 'group')
|
|
146
|
-
: ensureChannelPrefix(rawChannelId || String(info.id), 'private')
|
|
147
139
|
try {
|
|
148
140
|
const cachedMessages = await loadChatHistoryFromCache({
|
|
149
141
|
platform: String(authStore.loginInfo.platform ?? ''),
|
|
@@ -18,7 +18,7 @@ const logger = new Logger()
|
|
|
18
18
|
export interface LocalMsgRecord {
|
|
19
19
|
/** Bot 侧的消息 ID */
|
|
20
20
|
message_id: string
|
|
21
|
-
/** 会话 ID
|
|
21
|
+
/** 会话 ID:保存完整 channel_id,不剥离前缀 */
|
|
22
22
|
chat_id: number | string
|
|
23
23
|
/** 会话类型:"group" | "private" */
|
|
24
24
|
chat_type: string
|
|
@@ -115,18 +115,28 @@ function computeRawMessage(msg: any): string | null {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
function firstDefinedId(value: any, keys: string[]): number | string | undefined {
|
|
119
|
+
for (const key of keys) {
|
|
120
|
+
const current = value?.[key]
|
|
121
|
+
if (current != null && String(current) !== '') return current
|
|
122
|
+
}
|
|
123
|
+
return undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
118
126
|
function deriveChatId(selfId: string | number, msgs: any[]): number | string | undefined {
|
|
119
127
|
const firstMsg = msgs[0]
|
|
120
|
-
|
|
128
|
+
const firstInfo = firstMsg?.infoList ?? firstMsg
|
|
129
|
+
const chatId = firstDefinedId(firstInfo, ['channel_id', 'channelId', 'private_id', 'group_id', 'user_id'])
|
|
130
|
+
?? firstDefinedId(firstMsg, ['channel_id', 'channelId', 'private_id', 'group_id', 'user_id'])
|
|
121
131
|
if (chatId != null) return chatId
|
|
122
132
|
|
|
123
133
|
for (const item of msgs) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
const info = item?.infoList ?? item
|
|
135
|
+
if (info?.sender != null && String(info.sender) !== String(selfId)) {
|
|
136
|
+
return info.sender
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
|
-
return
|
|
139
|
+
return undefined
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
export function ensureChatIdOnMsgs(selfId: string | number, msgs: any[]): any[] {
|
|
@@ -135,14 +145,21 @@ export function ensureChatIdOnMsgs(selfId: string | number, msgs: any[]): any[]
|
|
|
135
145
|
|
|
136
146
|
return msgs.map((item: any) => {
|
|
137
147
|
if (!item?.infoList) return item
|
|
138
|
-
if (
|
|
148
|
+
if (
|
|
149
|
+
item.infoList.channel_id != null
|
|
150
|
+
|| item.infoList.channelId != null
|
|
151
|
+
|| item.infoList.private_id != null
|
|
152
|
+
|| item.infoList.group_id != null
|
|
153
|
+
|| item.infoList.user_id != null
|
|
154
|
+
) {
|
|
139
155
|
return item
|
|
140
156
|
}
|
|
141
157
|
return {
|
|
142
158
|
...item,
|
|
143
159
|
infoList: {
|
|
144
160
|
...item.infoList,
|
|
145
|
-
|
|
161
|
+
channel_id: chatId,
|
|
162
|
+
channelId: chatId,
|
|
146
163
|
},
|
|
147
164
|
}
|
|
148
165
|
})
|
|
@@ -158,12 +175,14 @@ export function msgToRecord(msg: any): LocalMsgRecord | null {
|
|
|
158
175
|
const messageId = msg.message_id
|
|
159
176
|
if (!messageId) return null
|
|
160
177
|
|
|
161
|
-
const
|
|
178
|
+
const info = msg.infoList ?? msg
|
|
179
|
+
const chatId = firstDefinedId(info, ['channel_id', 'channelId', 'private_id', 'group_id', 'user_id'])
|
|
180
|
+
?? firstDefinedId(msg, ['channel_id', 'channelId', 'private_id', 'group_id', 'user_id'])
|
|
162
181
|
if (chatId == null) return null
|
|
163
182
|
|
|
164
183
|
const chatType: string =
|
|
165
184
|
msg.message_type ?? (msg.group_id != null ? 'group' : 'private')
|
|
166
|
-
const senderId: number | string = msg.
|
|
185
|
+
const senderId: number | string = info.sender ?? msg.sender?.user_id
|
|
167
186
|
if (senderId == null) return null
|
|
168
187
|
|
|
169
188
|
const senderName: string | null =
|
|
@@ -490,7 +509,8 @@ function deserializeRecord(record: LocalMsgRecord): any {
|
|
|
490
509
|
message_id: record.message_id,
|
|
491
510
|
private_id: isGroup ? undefined : record.chat_id,
|
|
492
511
|
group_id: isGroup ? record.chat_id : undefined,
|
|
493
|
-
|
|
512
|
+
channel_id: record.chat_id,
|
|
513
|
+
channelId: record.chat_id,
|
|
494
514
|
sender: record.sender_id,
|
|
495
515
|
}
|
|
496
516
|
|
|
@@ -499,7 +519,9 @@ function deserializeRecord(record: LocalMsgRecord): any {
|
|
|
499
519
|
message_id: record.message_id,
|
|
500
520
|
message_type: record.chat_type,
|
|
501
521
|
// 根据 chat_type 恢复对应的 id 字段
|
|
502
|
-
...(isGroup ? { group_id: record.chat_id } : { user_id: record.
|
|
522
|
+
...(isGroup ? { group_id: record.chat_id } : { user_id: record.sender_id }),
|
|
523
|
+
channel_id: record.chat_id,
|
|
524
|
+
channelId: record.chat_id,
|
|
503
525
|
sender,
|
|
504
526
|
time: record.time,
|
|
505
527
|
timestamp_ms: timeMs || undefined,
|
|
@@ -509,6 +531,8 @@ function deserializeRecord(record: LocalMsgRecord): any {
|
|
|
509
531
|
infoList,
|
|
510
532
|
raw_message: record.raw_message ?? '',
|
|
511
533
|
revoked: record.revoked,
|
|
534
|
+
revoke: record.revoked,
|
|
535
|
+
revokeTime: record.revoked ? Date.now() / 1000 : undefined,
|
|
512
536
|
// 消息序列号(并非所有 Bot 都提供,可为 null)
|
|
513
537
|
...(record.seq != null ? { message_seq: record.seq, seq_id: record.seq } : {}),
|
|
514
538
|
// 标记来源为本地缓存,业务层可按需用此字段区分
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
getSessionId,
|
|
28
28
|
mergeSessionState,
|
|
29
29
|
normalizeSessionId,
|
|
30
|
+
upsertSessionContact,
|
|
30
31
|
} from './sessionUtil'
|
|
31
32
|
|
|
32
33
|
const logger = new Logger()
|
|
@@ -681,21 +682,31 @@ export async function sendMsgRaw(
|
|
|
681
682
|
chatStore.messageList = chatStore.messageList.concat([showMsg])
|
|
682
683
|
|
|
683
684
|
// 发送方不一定会上报自身消息事件,先用预发送消息同步会话预览。
|
|
684
|
-
const sessionId = normalizeSessionId(
|
|
685
|
+
const sessionId = normalizeSessionId(
|
|
686
|
+
targetChannelId
|
|
687
|
+
|| chatStore.chatInfo.show.channel_id
|
|
688
|
+
|| String(id).split('/')[0],
|
|
689
|
+
)
|
|
685
690
|
const session = contactStore.baseOnMsgList.get(sessionId) ??
|
|
686
691
|
findSessionContact(contactStore.userList, sessionId)
|
|
687
692
|
if (session) {
|
|
688
693
|
const raw = getMsgRawTxt(showMsg)
|
|
689
694
|
const senderName = authStore.loginInfo.nickname
|
|
690
|
-
Object.assign(session, {
|
|
691
|
-
message_id: showMsg.message_id,
|
|
692
|
-
raw_msg: type === 'group' && senderName ? `${senderName}: ${raw}` : raw,
|
|
693
|
-
raw_msg_base: raw,
|
|
694
|
-
time: showMsg.time * 1000,
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
695
|
+
Object.assign(session, {
|
|
696
|
+
message_id: showMsg.message_id,
|
|
697
|
+
raw_msg: type === 'group' && senderName ? `${senderName}: ${raw}` : raw,
|
|
698
|
+
raw_msg_base: raw,
|
|
699
|
+
time: showMsg.time * 1000,
|
|
700
|
+
})
|
|
701
|
+
if (type === 'group') {
|
|
702
|
+
// 群发送预览同步到联系人列表,同时进入群会话列表
|
|
703
|
+
contactStore.userList = upsertSessionContact(contactStore.userList, session)
|
|
704
|
+
} else if (session._groupMember) {
|
|
705
|
+
session._groupMember = false
|
|
706
|
+
}
|
|
707
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(sessionId), session)
|
|
708
|
+
updateBaseOnMsgList()
|
|
709
|
+
}
|
|
699
710
|
}
|
|
700
711
|
// 预发送保留 Base64 用于本地预览;真正发送前先写入后端临时目录
|
|
701
712
|
try {
|
|
@@ -762,7 +773,7 @@ export async function sendMsgRaw(
|
|
|
762
773
|
{
|
|
763
774
|
user_id: id.split('/')[0],
|
|
764
775
|
group_id: id.split('/')[1],
|
|
765
|
-
channel_id: String(targetChannelId ?? chatStore.chatInfo.show.channel_id ??
|
|
776
|
+
channel_id: String(targetChannelId ?? chatStore.chatInfo.show.channel_id ?? String(id).split('/')[0]),
|
|
766
777
|
message: msg,
|
|
767
778
|
},
|
|
768
779
|
echo + '_uuid_' + msgUUID,
|
|
@@ -773,7 +784,7 @@ export async function sendMsgRaw(
|
|
|
773
784
|
'send_msg',
|
|
774
785
|
{
|
|
775
786
|
user_id: id,
|
|
776
|
-
channel_id: String(targetChannelId ?? chatStore.chatInfo.show.channel_id ??
|
|
787
|
+
channel_id: String(targetChannelId ?? chatStore.chatInfo.show.channel_id ?? String(id)),
|
|
777
788
|
message: msg,
|
|
778
789
|
},
|
|
779
790
|
echo + '_uuid_' + msgUUID,
|
|
@@ -827,7 +838,7 @@ function normalizeBaseSessionMap() {
|
|
|
827
838
|
const seen = new Map<string, UserFriendElem & UserGroupElem>()
|
|
828
839
|
for (const [rawKey, item] of contactStore.baseOnMsgList) {
|
|
829
840
|
const id = getSessionDedupKey(item)
|
|
830
|
-
if (id
|
|
841
|
+
if (id === '0' || id === '') continue
|
|
831
842
|
const existing = seen.get(id)
|
|
832
843
|
if (existing && existing !== item) {
|
|
833
844
|
needNormalize = true
|
|
@@ -846,7 +857,7 @@ function normalizeBaseSessionMap() {
|
|
|
846
857
|
const canonical = new Map<string, UserFriendElem & UserGroupElem>()
|
|
847
858
|
contactStore.baseOnMsgList.forEach((item) => {
|
|
848
859
|
const id = getSessionDedupKey(item)
|
|
849
|
-
if (id === '
|
|
860
|
+
if (id === '0' || id === '') return
|
|
850
861
|
const existing = canonical.get(id)
|
|
851
862
|
if (existing && existing !== item) {
|
|
852
863
|
canonical.set(id, mergeSessionState(existing, item))
|
|
@@ -869,7 +880,7 @@ function getSessionList() {
|
|
|
869
880
|
|
|
870
881
|
const addSession = (item: UserFriendElem & UserGroupElem) => {
|
|
871
882
|
const id = getSessionDedupKey(item)
|
|
872
|
-
if (id === '
|
|
883
|
+
if (id === '0' || id === '') return
|
|
873
884
|
const existing = sessionMap.get(id)
|
|
874
885
|
if (existing && existing !== item) {
|
|
875
886
|
sessionMap.set(id, mergeSessionState(existing, item))
|
|
@@ -880,7 +891,7 @@ function getSessionList() {
|
|
|
880
891
|
|
|
881
892
|
if (settingsStore.sysConfig.session_display_mode === 'all') {
|
|
882
893
|
contactStore.userList.forEach((item) => {
|
|
883
|
-
addSession(item)
|
|
894
|
+
if (!item._groupMember) addSession(item)
|
|
884
895
|
})
|
|
885
896
|
}
|
|
886
897
|
|
|
@@ -894,11 +905,11 @@ function getSessionList() {
|
|
|
894
905
|
/**
|
|
895
906
|
* 刷新消息列表排序
|
|
896
907
|
*/
|
|
897
|
-
export function updateBaseOnMsgList() {
|
|
898
|
-
const contactStore = useContactStore()
|
|
899
|
-
const settingsStore = useSettingsStore()
|
|
900
|
-
normalizeBaseSessionMap()
|
|
901
|
-
const allList = getSessionList()
|
|
908
|
+
export function updateBaseOnMsgList() {
|
|
909
|
+
const contactStore = useContactStore()
|
|
910
|
+
const settingsStore = useSettingsStore()
|
|
911
|
+
normalizeBaseSessionMap()
|
|
912
|
+
const allList = getSessionList()
|
|
902
913
|
// 先更具 item.always_top 是不是 true 拆为两个数组
|
|
903
914
|
const topList = allList.filter((item) => item.always_top)
|
|
904
915
|
const normalList = allList.filter((item) => !item.always_top)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { Session } from '../elements/information'
|
|
2
2
|
|
|
3
3
|
export function normalizeGroupId(value: string): string {
|
|
4
|
-
|
|
5
|
-
const wrapped = raw.match(/^\[_?([\s\S]+?)_?\]$/)
|
|
6
|
-
return wrapped ? wrapped[1] : raw || value
|
|
4
|
+
return String(value)
|
|
7
5
|
}
|
|
8
6
|
|
|
9
7
|
export function normalizeSessionId(value: number | string): string {
|
|
@@ -27,12 +25,6 @@ export function getSessionAliases(item: Session): string[] {
|
|
|
27
25
|
getSessionId(item),
|
|
28
26
|
getLegacySessionId(item),
|
|
29
27
|
]
|
|
30
|
-
if (item.group_id !== undefined && item.group_id !== null) {
|
|
31
|
-
rawAliases.push(normalizeGroupId(String(item.group_id)))
|
|
32
|
-
}
|
|
33
|
-
if (item.user_id !== undefined && item.user_id !== null) {
|
|
34
|
-
rawAliases.push(String(item.user_id).replace(/^(?:private|direct):/i, ''))
|
|
35
|
-
}
|
|
36
28
|
const aliases = rawAliases.filter((value): value is number | string => {
|
|
37
29
|
const text = String(value ?? '')
|
|
38
30
|
return text !== '' && text !== '0'
|
|
@@ -44,11 +36,10 @@ export function getSessionDedupKey(item: Session): string {
|
|
|
44
36
|
const channelId = String(item.channel_id ?? item.channelId ?? '')
|
|
45
37
|
const groupId = String(item.group_id ?? '')
|
|
46
38
|
const userId = String(item.user_id ?? '')
|
|
47
|
-
if (
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
: `group:${normalizeGroupId(channelId)}`
|
|
39
|
+
if (channelId) return normalizeSessionId(channelId)
|
|
40
|
+
if (groupId) return normalizeSessionId(groupId)
|
|
41
|
+
if (userId) return normalizeSessionId(userId)
|
|
42
|
+
return ''
|
|
52
43
|
}
|
|
53
44
|
|
|
54
45
|
export function setSessionContact(
|
|
@@ -60,6 +51,21 @@ export function setSessionContact(
|
|
|
60
51
|
}
|
|
61
52
|
}
|
|
62
53
|
|
|
54
|
+
export function upsertSessionContact(
|
|
55
|
+
contacts: Session[],
|
|
56
|
+
item: Session,
|
|
57
|
+
): Session[] {
|
|
58
|
+
const key = getSessionDedupKey(item)
|
|
59
|
+
if (!key) return contacts
|
|
60
|
+
const index = contacts.findIndex((current) => getSessionDedupKey(current) === key)
|
|
61
|
+
if (index >= 0) {
|
|
62
|
+
const next = [...contacts]
|
|
63
|
+
Object.assign(next[index], item)
|
|
64
|
+
return next
|
|
65
|
+
}
|
|
66
|
+
return [...contacts, item]
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
export function findSessionContact(
|
|
64
70
|
contacts: Session[],
|
|
65
71
|
sessionId: number | string,
|
|
@@ -67,10 +73,6 @@ export function findSessionContact(
|
|
|
67
73
|
const target = normalizeSessionId(sessionId)
|
|
68
74
|
return contacts.find((item) => {
|
|
69
75
|
return getSessionAliases(item).some((alias) => alias === target)
|
|
70
|
-
|| (
|
|
71
|
-
Boolean(item.group_id) &&
|
|
72
|
-
normalizeGroupId(String(item.group_id)) === normalizeGroupId(target)
|
|
73
|
-
)
|
|
74
76
|
})
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -738,12 +738,6 @@ const searchRequestId = ref(0)
|
|
|
738
738
|
const forwardList = ref(contactStore.userList)
|
|
739
739
|
const chatImg = ref<any>(undefined)
|
|
740
740
|
const trueLang = getTrueLang()
|
|
741
|
-
let selfRefreshTimer: number | null = null
|
|
742
|
-
|
|
743
|
-
function ensureChannelPrefix(channelId: string, prefix: string): string {
|
|
744
|
-
if (/^(?:group|room|chat|channel|guild|private):/i.test(channelId)) return channelId
|
|
745
|
-
return `${prefix}:${channelId}`
|
|
746
|
-
}
|
|
747
741
|
|
|
748
742
|
function messageLocalTimeMs(item: any): number {
|
|
749
743
|
const local = Number(item?.local_time ?? item?.timestamp_ms ?? item?.time_ms ?? 0)
|
|
@@ -756,8 +750,8 @@ async function refreshSelfHistory() {
|
|
|
756
750
|
const id = chat.show.id
|
|
757
751
|
if (!id || id === 0 || details.value[3].open || tags.value.showForwardPan) return
|
|
758
752
|
const channelId = chat.show.type === 'group'
|
|
759
|
-
?
|
|
760
|
-
:
|
|
753
|
+
? String(chat.show.channel_id ?? id)
|
|
754
|
+
: String(chat.show.channel_id ?? id)
|
|
761
755
|
let cached: Record<string, unknown>[] = []
|
|
762
756
|
try {
|
|
763
757
|
cached = await loadChatHistoryFromCache({
|
|
@@ -798,22 +792,8 @@ async function refreshSelfHistory() {
|
|
|
798
792
|
}
|
|
799
793
|
|
|
800
794
|
function startSelfRefresh() {
|
|
801
|
-
if (selfRefreshTimer !== null) {
|
|
802
|
-
window.clearInterval(selfRefreshTimer)
|
|
803
|
-
selfRefreshTimer = null
|
|
804
|
-
}
|
|
805
795
|
if (!chat.show.id) return
|
|
806
796
|
void refreshSelfHistory()
|
|
807
|
-
selfRefreshTimer = window.setInterval(() => {
|
|
808
|
-
void refreshSelfHistory()
|
|
809
|
-
}, 2500)
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function stopSelfRefresh() {
|
|
813
|
-
if (selfRefreshTimer !== null) {
|
|
814
|
-
window.clearInterval(selfRefreshTimer)
|
|
815
|
-
selfRefreshTimer = null
|
|
816
|
-
}
|
|
817
797
|
}
|
|
818
798
|
|
|
819
799
|
//#region == 窗口移动相关 ==================================================
|
|
@@ -973,7 +953,6 @@ onMounted(() => {
|
|
|
973
953
|
})
|
|
974
954
|
|
|
975
955
|
onBeforeUnmount(() => {
|
|
976
|
-
stopSelfRefresh()
|
|
977
956
|
if (resizeMainInputFrame !== null) {
|
|
978
957
|
cancelAnimationFrame(resizeMainInputFrame)
|
|
979
958
|
resizeMainInputFrame = null
|
|
@@ -1217,19 +1196,24 @@ async function loadMoreHistory() {
|
|
|
1217
1196
|
}
|
|
1218
1197
|
uiStore.loadHistoryFail = false
|
|
1219
1198
|
|
|
1199
|
+
const channelId = String(
|
|
1200
|
+
chatStore.chatInfo.show.channel_id
|
|
1201
|
+
?? chatStore.chatInfo.show.id
|
|
1202
|
+
?? '',
|
|
1203
|
+
)
|
|
1220
1204
|
if (useMixedHistory) {
|
|
1221
1205
|
let localMsgs = [] as any[]
|
|
1222
1206
|
if (Number.isFinite(firstMsgTime)) {
|
|
1223
1207
|
localMsgs = await dbGetBeforeByTime(
|
|
1224
1208
|
authStore.loginInfo.uin,
|
|
1225
|
-
|
|
1209
|
+
channelId,
|
|
1226
1210
|
firstMsgTime,
|
|
1227
1211
|
20,
|
|
1228
1212
|
)
|
|
1229
1213
|
} else {
|
|
1230
1214
|
localMsgs = await dbGetBefore(
|
|
1231
1215
|
authStore.loginInfo.uin,
|
|
1232
|
-
|
|
1216
|
+
channelId,
|
|
1233
1217
|
firstMsgId,
|
|
1234
1218
|
20,
|
|
1235
1219
|
)
|
|
@@ -1251,12 +1235,6 @@ async function loadMoreHistory() {
|
|
|
1251
1235
|
}
|
|
1252
1236
|
}
|
|
1253
1237
|
|
|
1254
|
-
const type = chatStore.chatInfo.show.type
|
|
1255
|
-
const id = chatStore.chatInfo.show.id
|
|
1256
|
-
const rawChannelId = String(chatStore.chatInfo.show.channel_id ?? '')
|
|
1257
|
-
const channelId = type === 'group'
|
|
1258
|
-
? ensureChannelPrefix(rawChannelId || String(id), 'group')
|
|
1259
|
-
: ensureChannelPrefix(rawChannelId || String(id), 'private')
|
|
1260
1238
|
const cached = await loadChatHistoryFromCache({
|
|
1261
1239
|
platform: String(authStore.loginInfo.platform ?? ''),
|
|
1262
1240
|
selfId: String(authStore.loginInfo.uin ?? ''),
|
|
@@ -2030,6 +2008,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2030
2008
|
targetType as 'group' | 'user',
|
|
2031
2009
|
targetId,
|
|
2032
2010
|
msgBody,
|
|
2011
|
+
targetChannelId || undefined,
|
|
2033
2012
|
)
|
|
2034
2013
|
if (!result.ok) {
|
|
2035
2014
|
new PopInfo().add(PopType.ERR, $t('合并转发发送失败'))
|
|
@@ -2072,11 +2051,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2072
2051
|
scrollToMsg('chat-' + sentMsg.message_id, true)
|
|
2073
2052
|
})
|
|
2074
2053
|
}
|
|
2075
|
-
const sentChannelId =
|
|
2076
|
-
? (targetChannelId || targetId)
|
|
2077
|
-
: /^(?:private|direct):/i.test(targetChannelId || '')
|
|
2078
|
-
? targetChannelId
|
|
2079
|
-
: `private:${targetId}`
|
|
2054
|
+
const sentChannelId = result.channelId || targetChannelId || targetId
|
|
2080
2055
|
if (result.native && !result.messageId) {
|
|
2081
2056
|
// 原生路径已经由后端 before-send/发送拦截写入,缺少消息 id 时不再重复写入
|
|
2082
2057
|
} else {
|
|
@@ -2096,7 +2071,8 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2096
2071
|
kind: 'forward',
|
|
2097
2072
|
})
|
|
2098
2073
|
}
|
|
2099
|
-
const
|
|
2074
|
+
const targetSessionKey = targetChannelId || targetId
|
|
2075
|
+
const targetSession = contactStore.baseOnMsgList.get(normalizeSessionId(targetSessionKey))
|
|
2100
2076
|
if (targetSession) {
|
|
2101
2077
|
const preview = `[${$t('聊天记录')}]`
|
|
2102
2078
|
targetSession.raw_msg_base = preview
|
|
@@ -2104,7 +2080,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2104
2080
|
? `${String(authStore.loginInfo.nickname ?? '')}: ${preview}`
|
|
2105
2081
|
: preview
|
|
2106
2082
|
targetSession.time = Math.floor(sentTime / 1000)
|
|
2107
|
-
contactStore.baseOnMsgList.set(normalizeSessionId(
|
|
2083
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(targetSessionKey), targetSession)
|
|
2108
2084
|
updateBaseOnMsgList()
|
|
2109
2085
|
}
|
|
2110
2086
|
}
|
|
@@ -2153,8 +2129,9 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
|
|
|
2153
2129
|
|
|
2154
2130
|
function finishForward(data: UserFriendElem & UserGroupElem, id: string | number) {
|
|
2155
2131
|
cancelForward()
|
|
2156
|
-
|
|
2157
|
-
|
|
2132
|
+
const sessionKey = String(data.channel_id ?? data.channelId ?? id ?? '')
|
|
2133
|
+
if(contactStore.baseOnMsgList.get(normalizeSessionId(sessionKey)) == undefined) {
|
|
2134
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(sessionKey), data)
|
|
2158
2135
|
}
|
|
2159
2136
|
nextTick(() => {
|
|
2160
2137
|
const user = document.getElementById('user-' + id)
|
|
@@ -2267,7 +2244,15 @@ async function revokeMsg() {
|
|
|
2267
2244
|
?? chatStore.chatInfo.show.channel_id
|
|
2268
2245
|
?? chat.show.id,
|
|
2269
2246
|
)
|
|
2270
|
-
|
|
2247
|
+
try {
|
|
2248
|
+
await Connector.callApi('delete_msg', { channel_id: channelId, message_id: msgId })
|
|
2249
|
+
} catch {
|
|
2250
|
+
new PopInfo().add(PopType.ERR, $t('撤回失败'))
|
|
2251
|
+
return
|
|
2252
|
+
}
|
|
2253
|
+
msgData.revoke = true
|
|
2254
|
+
msgData.revoked = true
|
|
2255
|
+
msgData.revokeTime = Date.now() / 1000
|
|
2271
2256
|
}
|
|
2272
2257
|
|
|
2273
2258
|
async function reeditMsg() {
|
|
@@ -2629,13 +2614,31 @@ function sendVideo(file: File, fileName: string | null) {
|
|
|
2629
2614
|
sendMedia(file, fileName, 'video')
|
|
2630
2615
|
}
|
|
2631
2616
|
|
|
2617
|
+
async function isGifImage(file: File): Promise<boolean> {
|
|
2618
|
+
if (file.type === 'image/gif' || /\.gif$/i.test(file.name || '')) return true
|
|
2619
|
+
try {
|
|
2620
|
+
const buffer = await file.slice(0, 6).arrayBuffer()
|
|
2621
|
+
const bytes = new Uint8Array(buffer)
|
|
2622
|
+
return bytes.length >= 6
|
|
2623
|
+
&& bytes[0] === 0x47
|
|
2624
|
+
&& bytes[1] === 0x49
|
|
2625
|
+
&& bytes[2] === 0x46
|
|
2626
|
+
&& bytes[3] === 0x38
|
|
2627
|
+
&& (bytes[4] === 0x37 || bytes[4] === 0x39)
|
|
2628
|
+
&& bytes[5] === 0x61
|
|
2629
|
+
} catch {
|
|
2630
|
+
return false
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2632
2634
|
async function setImg(file: File | null) {
|
|
2633
2635
|
const popInfo = new PopInfo()
|
|
2634
2636
|
if (!file) return
|
|
2635
2637
|
if (!file.type.includes('image/')) return
|
|
2636
2638
|
if (file.size === 0) return
|
|
2639
|
+
const isGif = await isGifImage(file)
|
|
2637
2640
|
|
|
2638
|
-
if (file.size > 3145728) {
|
|
2641
|
+
if (file.size > 3145728 && !isGif) {
|
|
2639
2642
|
const options = { maxSizeMB: 3, useWebWorker: true }
|
|
2640
2643
|
try {
|
|
2641
2644
|
popInfo.add(PopType.INFO, $t('正在压缩图片 ……'))
|
|
@@ -3170,7 +3173,8 @@ async function handleInput(event: Event) {
|
|
|
3170
3173
|
searchDebounceTimer.value = setTimeout(async () => {
|
|
3171
3174
|
const results = await dbSearchMessages(
|
|
3172
3175
|
authStore.loginInfo.uin,
|
|
3173
|
-
chatStore.chatInfo.show.
|
|
3176
|
+
chatStore.chatInfo.show.channel_id
|
|
3177
|
+
?? chatStore.chatInfo.show.id,
|
|
3174
3178
|
value,
|
|
3175
3179
|
)
|
|
3176
3180
|
if (requestId !== searchRequestId.value || !details.value[3].open) return
|