koishi-plugin-chat-patch 5.3.0 → 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.
Files changed (77) hide show
  1. package/client/web/dist/assets/Chat-Clqxn7CB.js +28 -0
  2. package/client/web/dist/assets/{Chat-CTdyeaX2.css → Chat-DTEsebnY.css} +1 -1
  3. package/client/web/dist/assets/{MsgBody-DDacjWJ9.js → MsgBody-C0TKsXhC.js} +1 -1
  4. package/client/web/dist/assets/{MsgBody.vue_vue_type_script_setup_true_lang-B1dWyCB0.js → MsgBody.vue_vue_type_script_setup_true_lang-CCdBwlN_.js} +2 -2
  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-BlLut5xn.js → index-BA37EQGa.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 +7 -4
  28. package/client/web/src/components/BulletinBody.vue +1 -1
  29. package/client/web/src/components/DepPan.vue +1 -1
  30. package/client/web/src/components/FriendBody.vue +1 -0
  31. package/client/web/src/components/MsgBody.vue +1 -3
  32. package/client/web/src/components/ThemeColorPickerPan.vue +1 -1
  33. package/client/web/src/components/TinySessionBody.vue +1 -0
  34. package/client/web/src/components/UmamiInfoPan.vue +1 -1
  35. package/client/web/src/components/UpdatePan.vue +2 -2
  36. package/client/web/src/components/WelPan.vue +1 -1
  37. package/client/web/src/components/tooltip/UserInfoTooltip.vue +1 -1
  38. package/client/web/src/function/connect.ts +279 -86
  39. package/client/web/src/function/elements/information.ts +1 -0
  40. package/client/web/src/function/msg.ts +42 -23
  41. package/client/web/src/function/satori-model.ts +35 -13
  42. package/client/web/src/function/satori.ts +33 -2
  43. package/client/web/src/function/utils/appUtil.ts +3 -11
  44. package/client/web/src/function/utils/avatarUtil.ts +18 -3
  45. package/client/web/src/function/utils/localHistoryUtil.ts +36 -12
  46. package/client/web/src/function/utils/msgUtil.ts +32 -21
  47. package/client/web/src/function/utils/sessionUtil.ts +20 -18
  48. package/client/web/src/main.ts +5 -0
  49. package/client/web/src/pages/Chat.vue +50 -47
  50. package/client/web/src/pages/Friends.vue +28 -24
  51. package/client/web/src/pages/Info.vue +5 -5
  52. package/client/web/src/pages/Messages.vue +57 -36
  53. package/client/web/src/pages/Qzone.vue +2 -2
  54. package/client/web/src/pages/chat-view/ChatTerminal.vue +4 -1
  55. package/client/web/src/pages/chat-view/SystemNotice.vue +3 -3
  56. package/client/web/src/pages/options/OptAccount.vue +6 -4
  57. package/client/web/src/pages/options/OptDev.vue +45 -2
  58. package/lib/database.d.ts +58 -0
  59. package/lib/index.js +234 -82
  60. package/lib/media.d.ts +24 -0
  61. package/lib/recorder.d.ts +21 -0
  62. package/lib/self-message.d.ts +27 -0
  63. package/lib/types.d.ts +121 -0
  64. package/lib/web.d.ts +7 -0
  65. package/package.json +1 -1
  66. package/src/bootstrap.ts +1 -0
  67. package/src/cache.ts +1 -5
  68. package/src/config.ts +1 -1
  69. package/src/database.ts +52 -8
  70. package/src/index.ts +4 -4
  71. package/src/media.ts +97 -0
  72. package/src/recorder.ts +35 -26
  73. package/src/self-message.ts +68 -19
  74. package/src/types.ts +10 -0
  75. package/src/web.ts +57 -48
  76. package/client/web/dist/assets/Chat-B448YOeN.js +0 -28
  77. package/client/web/dist/assets/index-fW_zcub3.css +0 -1
@@ -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
 
@@ -21,10 +21,15 @@ import './assets/css/sys_notice.css'
21
21
 
22
22
  import { getPortableFileLang } from './function/utils/systemUtil'
23
23
  import { preloadPinyin } from './function/utils/pinyin'
24
+ import { avatarError, isAvatarImage } from './function/utils/avatarUtil'
24
25
 
25
26
  // 媒体资源由 <img>/<audio> 等元素加载时,失败事件只在前端控制台输出
26
27
  window.addEventListener('error', (event) => {
27
28
  const target = event.target
29
+ if (target instanceof HTMLImageElement && isAvatarImage(target)) {
30
+ avatarError(event)
31
+ return
32
+ }
28
33
  if (!(target instanceof HTMLImageElement) && !(target instanceof HTMLMediaElement)) return
29
34
  const source = target.currentSrc || target.src
30
35
  if (source.includes('/chat-patch/web/media') || source.includes('/chat-patch/api/media')) {
@@ -25,7 +25,7 @@
25
25
  <!-- 聊天基本信息 -->
26
26
  <div class="info">
27
27
  <font-awesome-icon class="back" :icon="['fas', 'angle-left']" @click="exitWin" />
28
- <img :src="chat.show.avatar || '/img/icons/icon.svg'" @error="avatarError">
28
+ <img data-avatar :src="chat.show.avatar || '/img/icons/icon.svg'" @error="avatarError">
29
29
  <div class="info">
30
30
  <p>
31
31
  {{ chat.show.name }}
@@ -181,7 +181,7 @@
181
181
  <div v-for="(item, index) in chat.info.jin_info.list"
182
182
  :key="'jin-' + index">
183
183
  <div>
184
- <img :src="item.sender_avatar || '/img/icons/icon.svg'">
184
+ <img data-avatar :src="item.sender_avatar || '/img/icons/icon.svg'">
185
185
  <div>
186
186
  <a>{{ item.sender_nick }}</a>
187
187
  <span>{{ item.sender_time ? Intl.DateTimeFormat(
@@ -517,7 +517,7 @@
517
517
  <div><font-awesome-icon :icon="['fas', 'arrow-up-right-from-square']" /></div>
518
518
  <a>{{ $t('跳转到消息') }}</a>
519
519
  </div>
520
- <div v-show="isDev" @click="consoleLogMsg">
520
+ <div @click="consoleLogMsg">
521
521
  <div><font-awesome-icon :icon="['fas', 'screwdriver-wrench']" /></div>
522
522
  <a>{{ $t('调试信息') }}</a>
523
523
  </div>
@@ -738,13 +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
- const isDev = import.meta.env.DEV
742
- let selfRefreshTimer: number | null = null
743
-
744
- function ensureChannelPrefix(channelId: string, prefix: string): string {
745
- if (/^(?:group|room|chat|channel|guild|private):/i.test(channelId)) return channelId
746
- return `${prefix}:${channelId}`
747
- }
748
741
 
749
742
  function messageLocalTimeMs(item: any): number {
750
743
  const local = Number(item?.local_time ?? item?.timestamp_ms ?? item?.time_ms ?? 0)
@@ -757,8 +750,8 @@ async function refreshSelfHistory() {
757
750
  const id = chat.show.id
758
751
  if (!id || id === 0 || details.value[3].open || tags.value.showForwardPan) return
759
752
  const channelId = chat.show.type === 'group'
760
- ? ensureChannelPrefix(String(chat.show.channel_id ?? id), 'group')
761
- : ensureChannelPrefix(String(chat.show.channel_id ?? id), 'private')
753
+ ? String(chat.show.channel_id ?? id)
754
+ : String(chat.show.channel_id ?? id)
762
755
  let cached: Record<string, unknown>[] = []
763
756
  try {
764
757
  cached = await loadChatHistoryFromCache({
@@ -799,22 +792,8 @@ async function refreshSelfHistory() {
799
792
  }
800
793
 
801
794
  function startSelfRefresh() {
802
- if (selfRefreshTimer !== null) {
803
- window.clearInterval(selfRefreshTimer)
804
- selfRefreshTimer = null
805
- }
806
795
  if (!chat.show.id) return
807
796
  void refreshSelfHistory()
808
- selfRefreshTimer = window.setInterval(() => {
809
- void refreshSelfHistory()
810
- }, 2500)
811
- }
812
-
813
- function stopSelfRefresh() {
814
- if (selfRefreshTimer !== null) {
815
- window.clearInterval(selfRefreshTimer)
816
- selfRefreshTimer = null
817
- }
818
797
  }
819
798
 
820
799
  //#region == 窗口移动相关 ==================================================
@@ -974,7 +953,6 @@ onMounted(() => {
974
953
  })
975
954
 
976
955
  onBeforeUnmount(() => {
977
- stopSelfRefresh()
978
956
  if (resizeMainInputFrame !== null) {
979
957
  cancelAnimationFrame(resizeMainInputFrame)
980
958
  resizeMainInputFrame = null
@@ -1218,19 +1196,24 @@ async function loadMoreHistory() {
1218
1196
  }
1219
1197
  uiStore.loadHistoryFail = false
1220
1198
 
1199
+ const channelId = String(
1200
+ chatStore.chatInfo.show.channel_id
1201
+ ?? chatStore.chatInfo.show.id
1202
+ ?? '',
1203
+ )
1221
1204
  if (useMixedHistory) {
1222
1205
  let localMsgs = [] as any[]
1223
1206
  if (Number.isFinite(firstMsgTime)) {
1224
1207
  localMsgs = await dbGetBeforeByTime(
1225
1208
  authStore.loginInfo.uin,
1226
- chatStore.chatInfo.show.id,
1209
+ channelId,
1227
1210
  firstMsgTime,
1228
1211
  20,
1229
1212
  )
1230
1213
  } else {
1231
1214
  localMsgs = await dbGetBefore(
1232
1215
  authStore.loginInfo.uin,
1233
- chatStore.chatInfo.show.id,
1216
+ channelId,
1234
1217
  firstMsgId,
1235
1218
  20,
1236
1219
  )
@@ -1252,12 +1235,6 @@ async function loadMoreHistory() {
1252
1235
  }
1253
1236
  }
1254
1237
 
1255
- const type = chatStore.chatInfo.show.type
1256
- const id = chatStore.chatInfo.show.id
1257
- const rawChannelId = String(chatStore.chatInfo.show.channel_id ?? '')
1258
- const channelId = type === 'group'
1259
- ? ensureChannelPrefix(rawChannelId || String(id), 'group')
1260
- : ensureChannelPrefix(rawChannelId || String(id), 'private')
1261
1238
  const cached = await loadChatHistoryFromCache({
1262
1239
  platform: String(authStore.loginInfo.platform ?? ''),
1263
1240
  selfId: String(authStore.loginInfo.uin ?? ''),
@@ -2031,6 +2008,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
2031
2008
  targetType as 'group' | 'user',
2032
2009
  targetId,
2033
2010
  msgBody,
2011
+ targetChannelId || undefined,
2034
2012
  )
2035
2013
  if (!result.ok) {
2036
2014
  new PopInfo().add(PopType.ERR, $t('合并转发发送失败'))
@@ -2073,11 +2051,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
2073
2051
  scrollToMsg('chat-' + sentMsg.message_id, true)
2074
2052
  })
2075
2053
  }
2076
- const sentChannelId = targetType === 'group'
2077
- ? (targetChannelId || targetId)
2078
- : /^(?:private|direct):/i.test(targetChannelId || '')
2079
- ? targetChannelId
2080
- : `private:${targetId}`
2054
+ const sentChannelId = result.channelId || targetChannelId || targetId
2081
2055
  if (result.native && !result.messageId) {
2082
2056
  // 原生路径已经由后端 before-send/发送拦截写入,缺少消息 id 时不再重复写入
2083
2057
  } else {
@@ -2097,7 +2071,8 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
2097
2071
  kind: 'forward',
2098
2072
  })
2099
2073
  }
2100
- const targetSession = contactStore.baseOnMsgList.get(normalizeSessionId(targetId))
2074
+ const targetSessionKey = targetChannelId || targetId
2075
+ const targetSession = contactStore.baseOnMsgList.get(normalizeSessionId(targetSessionKey))
2101
2076
  if (targetSession) {
2102
2077
  const preview = `[${$t('聊天记录')}]`
2103
2078
  targetSession.raw_msg_base = preview
@@ -2105,7 +2080,7 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
2105
2080
  ? `${String(authStore.loginInfo.nickname ?? '')}: ${preview}`
2106
2081
  : preview
2107
2082
  targetSession.time = Math.floor(sentTime / 1000)
2108
- contactStore.baseOnMsgList.set(normalizeSessionId(targetId), targetSession)
2083
+ contactStore.baseOnMsgList.set(normalizeSessionId(targetSessionKey), targetSession)
2109
2084
  updateBaseOnMsgList()
2110
2085
  }
2111
2086
  }
@@ -2154,8 +2129,9 @@ function forwardMsg(data: UserFriendElem & UserGroupElem) {
2154
2129
 
2155
2130
  function finishForward(data: UserFriendElem & UserGroupElem, id: string | number) {
2156
2131
  cancelForward()
2157
- if(contactStore.baseOnMsgList.get(normalizeSessionId(id)) == undefined) {
2158
- contactStore.baseOnMsgList.set(normalizeSessionId(id), data)
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)
2159
2135
  }
2160
2136
  nextTick(() => {
2161
2137
  const user = document.getElementById('user-' + id)
@@ -2268,7 +2244,15 @@ async function revokeMsg() {
2268
2244
  ?? chatStore.chatInfo.show.channel_id
2269
2245
  ?? chat.show.id,
2270
2246
  )
2271
- await Connector.callApi('delete_msg', { channel_id: channelId, message_id: msgId })
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
2272
2256
  }
2273
2257
 
2274
2258
  async function reeditMsg() {
@@ -2630,13 +2614,31 @@ function sendVideo(file: File, fileName: string | null) {
2630
2614
  sendMedia(file, fileName, 'video')
2631
2615
  }
2632
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
+
2633
2634
  async function setImg(file: File | null) {
2634
2635
  const popInfo = new PopInfo()
2635
2636
  if (!file) return
2636
2637
  if (!file.type.includes('image/')) return
2637
2638
  if (file.size === 0) return
2639
+ const isGif = await isGifImage(file)
2638
2640
 
2639
- if (file.size > 3145728) {
2641
+ if (file.size > 3145728 && !isGif) {
2640
2642
  const options = { maxSizeMB: 3, useWebWorker: true }
2641
2643
  try {
2642
2644
  popInfo.add(PopType.INFO, $t('正在压缩图片 ……'))
@@ -3171,7 +3173,8 @@ async function handleInput(event: Event) {
3171
3173
  searchDebounceTimer.value = setTimeout(async () => {
3172
3174
  const results = await dbSearchMessages(
3173
3175
  authStore.loginInfo.uin,
3174
- chatStore.chatInfo.show.id,
3176
+ chatStore.chatInfo.show.channel_id
3177
+ ?? chatStore.chatInfo.show.id,
3175
3178
  value,
3176
3179
  )
3177
3180
  if (requestId !== searchRequestId.value || !details.value[3].open) return
@@ -14,7 +14,7 @@
14
14
  <div id="friend-list" :class="'friend-list' + (uiStore.openSideBar ? ' open' : '')">
15
15
  <div>
16
16
  <div class="base">
17
- <span>{{ $t('列表') }}</span>
17
+ <span>{{ $t('联系人列表') }}</span>
18
18
  <div style="flex: 1" />
19
19
  <font-awesome-icon :icon="['fas', 'rotate-right']" @click="reloadUser" />
20
20
  </div>
@@ -56,17 +56,17 @@
56
56
  @click="userClick(item, $event)" />
57
57
  </div>
58
58
  <template v-else>
59
- <div v-for="bot in satoriLogins" :key="'bot-' + bot.selfId"
60
- :class="['robot-accordion', { expanded: bot.selfId === activeBotId }]">
59
+ <div v-for="bot in satoriLogins" :key="'bot-' + botKey(bot.platform, bot.selfId)"
60
+ :class="['robot-accordion', { expanded: botKey(bot.platform, bot.selfId) === activeBotId }]">
61
61
  <div class="robot-accordion-header" @click="toggleRobot(bot)">
62
- <img :src="bot.avatar || '/img/icons/icon.svg'" :alt="bot.name" @error="avatarError">
62
+ <img data-avatar :src="bot.avatar || '/img/icons/icon.svg'" :alt="bot.name" @error="avatarError">
63
63
  <div>
64
64
  <span :title="bot.name">{{ bot.name }}</span>
65
65
  <small>{{ bot.platform }}</small>
66
66
  </div>
67
- <font-awesome-icon :icon="['fas', bot.selfId === activeBotId ? 'angle-up' : 'angle-down']" />
67
+ <font-awesome-icon :icon="['fas', botKey(bot.platform, bot.selfId) === activeBotId ? 'angle-up' : 'angle-down']" />
68
68
  </div>
69
- <div v-if="bot.selfId === activeBotId" class="robot-accordion-content">
69
+ <div v-if="botKey(bot.platform, bot.selfId) === activeBotId" class="robot-accordion-content">
70
70
  <div v-if="!contactStore.friendLoading && contactStore.userList.length === 0"
71
71
  class="empty-state">
72
72
  {{ $t('空') }}
@@ -183,8 +183,8 @@
183
183
 
184
184
  import { reloadUsers } from '../function/utils/appUtil'
185
185
  import { Connector, flushPendingBotEvents, loadContactsFromCache, login as loginInfo } from '../function/connect'
186
- import { getActiveBot, getLogins, setActiveBot } from '../function/satori'
187
- import { normalizeGroupId, normalizeSessionId } from '../function/utils/sessionUtil'
186
+ import { botKey, getActiveBot, getLogins, setActiveBot } from '../function/satori'
187
+ import { normalizeSessionId } from '../function/utils/sessionUtil'
188
188
  import { avatarError } from '../function/utils/avatarUtil'
189
189
  import { backend } from '../runtime/backend'
190
190
  import { matchPinyin } from '../function/utils/pinyin'
@@ -220,7 +220,12 @@
220
220
  const searchInfo = ref('')
221
221
  const classStatus = ref<{ [key: string]: boolean }>({})
222
222
  const satoriLogins = ref<Array<{ platform: string; selfId: string; name: string; avatar?: string; status?: number; features?: string[] }>>(getLogins())
223
- const activeBotId = ref(getActiveBot()?.selfId ?? loginInfo.selectedSatoriBot ?? '')
223
+ const active = getActiveBot()
224
+ const activeBotId = ref(
225
+ active?.platform && active.selfId
226
+ ? botKey(active.platform, active.selfId)
227
+ : loginInfo.selectedSatoriBot ?? '',
228
+ )
224
229
 
225
230
  watch(() => loginInfo.satoriLogins, (list) => {
226
231
  satoriLogins.value = list ?? []
@@ -230,7 +235,7 @@
230
235
  }, { immediate: true })
231
236
 
232
237
  const currentBotName = computed(() => {
233
- return satoriLogins.value.find((item) => item.selfId === activeBotId.value)?.name ?? ''
238
+ return satoriLogins.value.find((item) => botKey(item.platform, item.selfId) === activeBotId.value)?.name ?? ''
234
239
  })
235
240
 
236
241
  const allContacts = computed<ContactWithBot[]>(() => {
@@ -248,19 +253,17 @@
248
253
  || '',
249
254
  )
250
255
  const groupId = String(item.group_id || '')
251
- const id = groupId
252
- ? `group:${normalizeGroupId(rawId || groupId)}`
253
- : `user:${rawId.replace(/^(?:private|direct):/i, '')}`
256
+ const id = `${groupId ? 'group' : 'user'}|${String(rawId || groupId || item.user_id || '')}`
254
257
  const key = [bot?.platform ?? '', bot?.selfId ?? '', id]
255
258
  .map((value) => encodeURIComponent(String(value)))
256
259
  .join(':')
257
260
  map.set(key, { ...item, _bot: bot })
258
261
  }
259
262
  }
260
- const currentBot = satoriLogins.value.find((item) => item.selfId === activeBotId.value)
263
+ const currentBot = satoriLogins.value.find((item) => botKey(item.platform, item.selfId) === activeBotId.value)
261
264
  addContacts(contactStore.userList, currentBot)
262
- for (const [selfId, state] of contactStore.botStates) {
263
- const bot = satoriLogins.value.find((item) => item.selfId === selfId)
265
+ for (const [stateKey, state] of contactStore.botStates) {
266
+ const bot = satoriLogins.value.find((item) => botKey(item.platform, item.selfId) === stateKey)
264
267
  addContacts(state.userList, bot)
265
268
  }
266
269
  return [...map.values()]
@@ -284,9 +287,10 @@
284
287
  }
285
288
 
286
289
  function selectRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
287
- if (bot.selfId === activeBotId.value) return
290
+ const key = botKey(bot.platform, bot.selfId)
291
+ if (key === activeBotId.value) return
288
292
  snapshotCurrentBot()
289
- activeBotId.value = bot.selfId
293
+ activeBotId.value = key
290
294
  setActiveBot(bot.platform, bot.selfId)
291
295
  loginInfo.uin = bot.selfId
292
296
  loginInfo.nickname = bot.name
@@ -299,9 +303,9 @@
299
303
  platform: bot.platform,
300
304
  avatar: bot.avatar,
301
305
  satoriLogins: satoriLogins.value,
302
- selectedSatoriBot: bot.selfId,
306
+ selectedSatoriBot: key,
303
307
  }
304
- const saved = contactStore.botStates.get(bot.selfId)
308
+ const saved = contactStore.botStates.get(key)
305
309
  if (saved) {
306
310
  contactStore.userList = saved.userList
307
311
  contactStore.baseOnMsgList.clear()
@@ -339,7 +343,7 @@
339
343
  }
340
344
 
341
345
  function toggleRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
342
- if (activeBotId.value === bot.selfId) {
346
+ if (activeBotId.value === botKey(bot.platform, bot.selfId)) {
343
347
  backToRobots()
344
348
  } else {
345
349
  selectRobot(bot)
@@ -395,7 +399,7 @@
395
399
  }
396
400
 
397
401
  function userClick(data: ContactWithBot, event: Event) {
398
- if (data._bot && data._bot.selfId !== activeBotId.value) {
402
+ if (data._bot && botKey(data._bot.platform, data._bot.selfId) !== activeBotId.value) {
399
403
  selectRobot(data._bot)
400
404
  }
401
405
  const sender = event.currentTarget as HTMLDivElement
@@ -659,12 +663,12 @@
659
663
  font-size: 0.9rem;
660
664
  }
661
665
 
662
- @media (max-width: 700px) {
666
+ @media (max-width: 800px) {
663
667
  .exp-header:not(.open) {
664
668
  display: none;
665
669
  }
666
670
  }
667
- @media (max-width: 500px) {
671
+ @media (max-width: 800px) {
668
672
  .exp-header > span {
669
673
  display: block !important;
670
674
  }
@@ -16,7 +16,7 @@
16
16
  </header>
17
17
  <div class="chat-info-base user">
18
18
  <div>
19
- <img :src="chat.show.avatar || '/img/icons/icon.svg'" @error="avatarError">
19
+ <img data-avatar :src="chat.show.avatar || '/img/icons/icon.svg'" @error="avatarError">
20
20
  <div>
21
21
  <a>{{ chat.show.name }}</a>
22
22
  <span>{{ chat.show.id }}</span>
@@ -109,7 +109,7 @@
109
109
  :item-size="60"
110
110
  key-field="user_id">
111
111
  <div class="member-item edit">
112
- <img alt="nk" loading="lazy"
112
+ <img alt="nk" loading="lazy" data-avatar
113
113
  :src="memberAvatar(item) || '/img/icons/icon.svg'">
114
114
  <div>
115
115
  <a @click="startChat(item)">{{
@@ -128,7 +128,7 @@
128
128
  </BcTab>
129
129
  <div :class="'ss-card user-config' + (Object.keys(showUserConfig).length > 0 ? ' show' : '')">
130
130
  <div>
131
- <img alt="nk" :src="showUserConfig.avatar || '/img/icons/icon.svg'">
131
+ <img alt="nk" data-avatar :src="showUserConfig.avatar || '/img/icons/icon.svg'">
132
132
  <div>
133
133
  <a>{{ showUserConfig.card != '' ? showUserConfig.card : showUserConfig.nickname }}</a>
134
134
  <span>{{ showUserConfig.user_id }}</span>
@@ -246,7 +246,7 @@ function userChannelId(): string {
246
246
  || props.chat.show.channelId
247
247
  || props.chat.info.user_info.channel_id
248
248
  || props.chat.info.user_info.channelId
249
- || `private:${props.chat.show.id}`,
249
+ || String(props.chat.show.id),
250
250
  )
251
251
  }
252
252
 
@@ -256,7 +256,7 @@ function groupChannelId(): string {
256
256
  || props.chat.show.channelId
257
257
  || props.chat.info.group_info.channel_id
258
258
  || props.chat.info.group_info.channelId
259
- || `group:${props.chat.show.id}`,
259
+ || String(props.chat.show.id),
260
260
  )
261
261
  }
262
262