koishi-plugin-chat-patch 5.3.1 → 5.5.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.
Files changed (65) hide show
  1. package/client/web/dist/assets/Chat-B5Jdrz_2.js +28 -0
  2. package/client/web/dist/assets/{Chat-C-rmzMIw.css → Chat-DTEsebnY.css} +1 -1
  3. package/client/web/dist/assets/{MsgBody-h4C1nnBQ.js → MsgBody-tPnKSZOs.js} +1 -1
  4. package/client/web/dist/assets/{MsgBody.vue_vue_type_script_setup_true_lang-a902ps2r.js → MsgBody.vue_vue_type_script_setup_true_lang-ClDybRo_.js} +1 -1
  5. package/client/web/dist/assets/{append_darwin-CqBNU62J.css → append_darwin-Cql-dV6d.css} +1 -1
  6. package/client/web/dist/assets/{append_full_vibrancy-j0D2LM9m.css → append_full_vibrancy-ih3loy1O.css} +1 -1
  7. package/client/web/dist/assets/{append_linux-uhxeBqeC.css → append_linux-B3LvR8KB.css} +1 -1
  8. package/client/web/dist/assets/{append_new-407Lo5_V.css → append_new-DfhN_NX9.css} +1 -1
  9. package/client/web/dist/assets/{append_vibrancy-6FHctkOY.css → append_vibrancy-D3pVsmAa.css} +1 -1
  10. package/client/web/dist/assets/{append_win32-srmD-78y.css → append_win32-Dh9JlmlE.css} +1 -1
  11. package/client/web/dist/assets/index-B71pPwae.css +1 -0
  12. package/client/web/dist/assets/{index-CdTmWcO4.js → index-CSGfOsfr.js} +97 -76
  13. package/client/web/dist/index.html +2 -2
  14. package/client/web/src/App.vue +2 -2
  15. package/client/web/src/assets/css/append/append_darwin.css +2 -2
  16. package/client/web/src/assets/css/append/append_full_vibrancy.css +2 -2
  17. package/client/web/src/assets/css/append/append_linux.css +2 -2
  18. package/client/web/src/assets/css/append/append_new.css +2 -2
  19. package/client/web/src/assets/css/append/append_vibrancy.css +1 -1
  20. package/client/web/src/assets/css/append/append_win32.css +2 -2
  21. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +1 -1
  22. package/client/web/src/assets/css/chat.css +4 -3
  23. package/client/web/src/assets/css/msg.css +7 -3
  24. package/client/web/src/assets/css/options.css +2 -2
  25. package/client/web/src/assets/css/view.css +14 -3
  26. package/client/web/src/assets/l10n/zh-CN.po +21 -0
  27. package/client/web/src/components/AboutPan.vue +5 -3
  28. package/client/web/src/components/DepPan.vue +1 -1
  29. package/client/web/src/components/MsgBody.vue +0 -3
  30. package/client/web/src/components/ThemeColorPickerPan.vue +1 -1
  31. package/client/web/src/components/UmamiInfoPan.vue +1 -1
  32. package/client/web/src/components/WelPan.vue +1 -1
  33. package/client/web/src/function/connect.ts +232 -72
  34. package/client/web/src/function/elements/information.ts +1 -0
  35. package/client/web/src/function/msg.ts +43 -24
  36. package/client/web/src/function/satori-model.ts +47 -15
  37. package/client/web/src/function/satori.ts +2 -1
  38. package/client/web/src/function/utils/appUtil.ts +3 -11
  39. package/client/web/src/function/utils/localHistoryUtil.ts +36 -12
  40. package/client/web/src/function/utils/msgUtil.ts +32 -21
  41. package/client/web/src/function/utils/sessionUtil.ts +20 -18
  42. package/client/web/src/pages/Chat.vue +47 -43
  43. package/client/web/src/pages/Friends.vue +5 -7
  44. package/client/web/src/pages/Info.vue +2 -2
  45. package/client/web/src/pages/Messages.vue +37 -23
  46. package/client/web/src/pages/chat-view/ChatTerminal.vue +4 -1
  47. package/client/web/src/pages/options/OptDev.vue +45 -2
  48. package/lib/database.d.ts +58 -0
  49. package/lib/index.js +220 -68
  50. package/lib/media.d.ts +24 -0
  51. package/lib/recorder.d.ts +21 -0
  52. package/lib/self-message.d.ts +27 -0
  53. package/lib/types.d.ts +6 -0
  54. package/lib/web.d.ts +7 -0
  55. package/package.json +1 -1
  56. package/src/cache.ts +1 -5
  57. package/src/database.ts +52 -8
  58. package/src/index.ts +4 -4
  59. package/src/media.ts +97 -0
  60. package/src/recorder.ts +28 -19
  61. package/src/self-message.ts +68 -19
  62. package/src/types.ts +6 -0
  63. package/src/web.ts +49 -39
  64. package/client/web/dist/assets/Chat-Bo5JdcNg.js +0 -28
  65. 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
- const raw = value.replace(/^(?:group|room|chat|channel|guild):/i, '').trim()
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 {
@@ -231,16 +229,23 @@ function normalizeAtText(name: string, id: string): string {
231
229
  }
232
230
 
233
231
  function containsElementMarkup(source: string): boolean {
234
- return /<(img|image|audio|video|file|mface|face|quote|at|forward|json|xml|markdown|keyboard|p|br|i18n|a|text|sharp)(?:\s|\/|>)/i.test(source)
232
+ return /(?:<|&(?:amp;)?lt;)(?:img|image|audio|video|file|mface|face|faceType|quote|at|forward|json|xml|markdown|keyboard|p|br|i18n|a|text|sharp)(?:\s|\/|>|&(?:amp;)?gt;)/i.test(source)
235
233
  }
236
234
 
237
235
  // QQ 官方机器人会把部分大表情/图片消息的私有标记塞进 content,渲染前需要去掉
238
236
  function cleanQqFaceMarkup(source: string): string {
239
237
  return source
240
238
  .replace(/<faceType\b[^>]*>/gi, '')
241
- .replace(/&lt;faceType\b[^>]*&gt;/gi, '')
239
+ .replace(/&(?:amp;)?lt;faceType\b[^>]*&(?:amp;)?gt;/gi, '')
242
240
  }
243
241
 
242
+ const QQ_EXPRESSION_PLACEHOLDER = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(
243
+ '<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120">'
244
+ + '<rect width="120" height="120" rx="12" fill="#f3f4f6"/>'
245
+ + '<text x="60" y="62" text-anchor="middle" dominant-baseline="middle" font-size="18" fill="#333">表情消息</text>'
246
+ + '</svg>',
247
+ )
248
+
244
249
  function splitMarkupTextSegments(
245
250
  segments: Array<Record<string, unknown>>,
246
251
  ): Array<Record<string, unknown>> {
@@ -257,7 +262,18 @@ function splitMarkupTextSegments(
257
262
  }
258
263
 
259
264
  export function parseSatoriMarkup(source: string): Array<Record<string, unknown>> {
265
+ const hadQqFaceMarkup = /(?:&(?:amp;)?lt;|<)faceType\b/i.test(source)
260
266
  source = cleanQqFaceMarkup(source)
267
+ if (hadQqFaceMarkup && !source.trim()) {
268
+ return [{
269
+ type: 'image',
270
+ file: QQ_EXPRESSION_PLACEHOLDER,
271
+ url: QQ_EXPRESSION_PLACEHOLDER,
272
+ summary: '表情消息',
273
+ width: 120,
274
+ height: 120,
275
+ }]
276
+ }
261
277
  const result: Array<Record<string, unknown>> = []
262
278
  let index = 0
263
279
  while (index < source.length) {
@@ -449,11 +465,11 @@ export function satoriEventToOneBot(
449
465
  if (type === 'message' || type === 'message-created' || type === 'send') {
450
466
  const isGroup = isGroupChannel(channel, guild)
451
467
  const userId = getString(user.id)
452
- || (isGroup ? '' : getString(channel.id).replace(/^private:/, ''))
468
+ || (isGroup ? '' : getString(channel.id))
453
469
  const groupId = isGroup
454
470
  ? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
455
471
  : ''
456
- const directChannelId = isGroup ? groupId : userId
472
+ const directChannelId = isGroup ? groupId : getString(channel.id) || userId
457
473
  const nickname = getString(user.name) || getString(user.nick) || userId
458
474
  const card = getString(member.nick) || getString(member.name) || ''
459
475
  return {
@@ -470,7 +486,7 @@ export function satoriEventToOneBot(
470
486
  group_avatar: isGroup
471
487
  ? (usableAvatar(guild.avatar) || usableAvatar(channel.avatar) || buildQqGroupAvatar(platform, groupId) || undefined)
472
488
  : undefined,
473
- channel_id: getString(channel.id) || (isGroup ? groupId : `private:${userId}`),
489
+ channel_id: getString(channel.id) || (isGroup ? groupId : userId),
474
490
  guild_id: getString(guild.id),
475
491
  target_id: selfId,
476
492
  message: messageSegments(message),
@@ -486,7 +502,7 @@ export function satoriEventToOneBot(
486
502
  message_id: getString(message.id),
487
503
  private_id: isGroup ? '' : directChannelId,
488
504
  group_id: groupId,
489
- channel_id: getString(channel.id) || (isGroup ? groupId : `private:${userId}`),
505
+ channel_id: getString(channel.id) || (isGroup ? groupId : userId),
490
506
  guild_id: getString(guild.id),
491
507
  target_id: selfId,
492
508
  sender: userId,
@@ -533,6 +549,24 @@ export function satoriEventToOneBot(
533
549
  }
534
550
  }
535
551
 
552
+ if (type === 'message-deleted') {
553
+ const isGroup = isGroupChannel(channel, guild)
554
+ const userId = getString(user.id) || (isGroup ? '' : getString(channel.id))
555
+ const groupId = isGroup
556
+ ? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
557
+ : ''
558
+ return {
559
+ ...base,
560
+ post_type: 'notice',
561
+ notice_type: isGroup ? 'group_recall' : 'friend_recall',
562
+ group_id: groupId,
563
+ user_id: userId,
564
+ message_id: getString(message.id),
565
+ channel_id: getString(channel.id) || (isGroup ? groupId : userId),
566
+ guild_id: getString(guild.id),
567
+ }
568
+ }
569
+
536
570
  return null
537
571
  }
538
572
 
@@ -637,9 +671,7 @@ export function mapAction(action: string, params: Record<string, unknown>): {
637
671
  const guildId = getString(params.group_id) || getString(params.guild_id)
638
672
  const userId = getString(params.user_id)
639
673
  const messageId = getString(params.message_id)
640
- const directChannel = userId && !/^(?:group|room|chat|channel|guild|private):/i.test(userId)
641
- ? `private:${userId}`
642
- : userId
674
+ const directChannel = userId
643
675
  const channelId = getString(params.channel_id) || guildId || directChannel || userId
644
676
  const content = Array.isArray(params.message)
645
677
  ? serializeSatoriContent(params.message)
@@ -696,8 +728,8 @@ function messageListFromResponse(data: unknown): unknown[] {
696
728
  ? normalizeGroupId(getString(guild.id)) || normalizeGroupId(getString(channel.id))
697
729
  : ''
698
730
  const userId = getString(user.id)
699
- || (isGroup ? '' : getString(channel.id).replace(/^private:/, ''))
700
- const directChannelId = isGroup ? groupId : userId
731
+ || (isGroup ? '' : getString(channel.id))
732
+ const directChannelId = isGroup ? groupId : getString(channel.id) || userId
701
733
  return {
702
734
  message_id: getString(message.id),
703
735
  sn: getNumber(message.sn),
@@ -709,7 +741,7 @@ function messageListFromResponse(data: unknown): unknown[] {
709
741
  channel_type: channel.type,
710
742
  group_id: groupId,
711
743
  user_id: groupId ? userId : directChannelId,
712
- channel_id: getString(channel.id) || (isGroup ? groupId : `private:${userId}`),
744
+ channel_id: getString(channel.id) || (isGroup ? groupId : userId),
713
745
  guild_id: getString(guild.id),
714
746
  sender: {
715
747
  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
- throw new Error(`Satori API 返回了非 JSON 内容: ${text.slice(0, 120)}`)
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 cacheKey = [authStore.loginInfo.platform, authStore.loginInfo.uin, info.id].map(encodeKeyPart).join(':')
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
- info.id,
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(group_id user_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
- let chatId: number | string | undefined = firstMsg?.infoList?.group_id ?? firstMsg?.infoList?.target_id
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
- if (item?.infoList?.sender != null && String(item.infoList.sender) !== String(selfId)) {
125
- chatId = item.infoList.sender
126
- break
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 chatId
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 (item.infoList.group_id != null || item.infoList.target_id != null) {
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
- target_id: chatId,
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 chatId: number | string = msg.infoList.group_id ?? msg.infoList.target_id
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.infoList.sender
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
- target_id: isGroup ? undefined : record.chat_id,
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.chat_id }),
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(String(id).split('/')[0])
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
- contactStore.baseOnMsgList.set(normalizeSessionId(sessionId), session)
697
- updateBaseOnMsgList()
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 ?? `private:${String(id).split('/')[0]}`),
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 ?? (/^(?:group|room|chat|channel|guild|private):/i.test(String(id)) ? String(id) : `private:${String(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.endsWith(':0') || id.endsWith(':')) continue
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 === 'group:0' || id === 'group:' || id === 'user:0' || id === 'user:') return
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 === 'group:0' || id === 'group:' || id === 'user:0' || id === 'user:') return
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
- const raw = value.replace(/^(?:group|room|chat|channel|guild):/i, '').trim()
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 (groupId) return `group:${normalizeGroupId(channelId || groupId)}`
48
- if (userId) return `user:${userId.replace(/^(?:private|direct):/i, '')}`
49
- return /^(?:private|direct):/i.test(channelId)
50
- ? `user:${channelId.replace(/^(?:private|direct):/i, '')}`
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