koishi-plugin-chat-patch 5.5.1 → 5.6.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 (43) hide show
  1. package/client/vue/index.vue +8 -1
  2. package/client/web/dist/assets/{Chat-DTEsebnY.css → Chat-BXVWj5Bd.css} +1 -1
  3. package/client/web/dist/assets/{Chat-B5Jdrz_2.js → Chat-m4-U5uHa.js} +4 -4
  4. package/client/web/dist/assets/{MsgBody-tPnKSZOs.js → MsgBody-CzrxyiFO.js} +1 -1
  5. package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-BBF1uxm6.js +67 -0
  6. package/client/web/dist/assets/{index-CSGfOsfr.js → index-EJ2CcODr.js} +70 -76
  7. package/client/web/dist/assets/index-X9hFOAza.css +1 -0
  8. package/client/web/dist/index.html +2 -2
  9. package/client/web/src/App.vue +7 -0
  10. package/client/web/src/assets/css/chat.css +6 -4
  11. package/client/web/src/assets/l10n/zh-CN.po +0 -6
  12. package/client/web/src/components/History.vue +27 -0
  13. package/client/web/src/components/MsgBody.vue +18 -9
  14. package/client/web/src/function/connect.ts +208 -46
  15. package/client/web/src/function/msg.ts +7 -5
  16. package/client/web/src/function/option.ts +1 -1
  17. package/client/web/src/function/satori.ts +64 -0
  18. package/client/web/src/pages/Chat.vue +0 -4
  19. package/client/web/src/pages/Friends.vue +7 -9
  20. package/client/web/src/pages/Messages.vue +2 -2
  21. package/client/web/src/pages/options/OptDev.vue +1 -25
  22. package/client/web/src/pages/options/OptFunction.vue +23 -0
  23. package/client/web/tsconfig.tsbuildinfo +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/style.css +1 -1
  26. package/lib/bootstrap.d.ts +6 -0
  27. package/lib/database.d.ts +5 -2
  28. package/lib/gateway.d.ts +46 -0
  29. package/lib/index.js +1468 -131
  30. package/lib/recorder.d.ts +3 -5
  31. package/lib/satori.d.ts +3 -0
  32. package/lib/types.d.ts +17 -0
  33. package/package.json +4 -1
  34. package/src/bootstrap.ts +5 -10
  35. package/src/database.ts +745 -662
  36. package/src/gateway.ts +259 -0
  37. package/src/index.ts +63 -53
  38. package/src/recorder.ts +97 -77
  39. package/src/satori.ts +19 -0
  40. package/src/server.d.ts +26 -26
  41. package/src/types.ts +19 -0
  42. package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-ClDybRo_.js +0 -67
  43. package/client/web/dist/assets/index-B71pPwae.css +0 -1
@@ -4,7 +4,7 @@ import { reactive } from 'vue'
4
4
  import { dispatch, msgPreprocess } from './msg'
5
5
  import {
6
6
  botKey,
7
- connect as connectSatori,
7
+ connectBackend,
8
8
  getActiveBot,
9
9
  getBlockedPlatforms,
10
10
  getBootstrap,
@@ -995,30 +995,34 @@ function onSatoriReady(logins: Array<{ platform: string; selfId: string; name: s
995
995
  }
996
996
  const first = visibleLogins[0]
997
997
  if (!first) return
998
+ const current = getActiveBot()
999
+ const selected = (current && visibleLogins.find((item) => {
1000
+ return item.platform === current.platform && item.selfId === current.selfId
1001
+ })) ?? first
998
1002
 
999
- setActiveBot(first.platform, first.selfId)
1003
+ setActiveBot(selected.platform, selected.selfId)
1000
1004
  setJsonMap()
1001
- login.uin = first.selfId
1002
- login.nickname = first.name
1003
- login.platform = first.platform
1005
+ login.uin = selected.selfId
1006
+ login.nickname = selected.name
1007
+ login.platform = selected.platform
1004
1008
  login.satoriLogins = visibleLogins
1005
- login.selectedSatoriBot = botKey(first.platform, first.selfId)
1009
+ login.selectedSatoriBot = botKey(selected.platform, selected.selfId)
1006
1010
  authStore.loginInfo = {
1007
- uin: first.selfId,
1008
- user_id: first.selfId,
1009
- nickname: first.name,
1010
- platform: first.platform,
1011
- avatar: first.avatar,
1011
+ uin: selected.selfId,
1012
+ user_id: selected.selfId,
1013
+ nickname: selected.name,
1014
+ platform: selected.platform,
1015
+ avatar: selected.avatar,
1012
1016
  satoriLogins: visibleLogins,
1013
- selectedSatoriBot: botKey(first.platform, first.selfId),
1017
+ selectedSatoriBot: botKey(selected.platform, selected.selfId),
1014
1018
  }
1015
1019
  authStore.botInfo = {
1016
1020
  app_name: 'Satori',
1017
1021
  app_version: '1.0',
1018
- platform: first.platform,
1022
+ platform: selected.platform,
1019
1023
  }
1020
- saveConnectionToHistory(login.address, login.token, first.selfId, first.name)
1021
- flushPendingBotEvents(first.platform, first.selfId)
1024
+ saveConnectionToHistory(login.address, login.token, selected.selfId, selected.name)
1025
+ flushPendingBotEvents(selected.platform, selected.selfId)
1022
1026
  void loadContactsFromCache()
1023
1027
  }
1024
1028
 
@@ -1622,9 +1626,18 @@ async function appendContactCache(
1622
1626
  function buildFriendItem(raw: unknown, data: unknown): Record<string, unknown> {
1623
1627
  const root = getObject(data)
1624
1628
  const user = getObject(root.user) || getObject(root)
1625
- const name = String(user.name ?? user.nick ?? user.username ?? '')
1629
+ const rawItem = getObject(raw)
1630
+ const name = getString(
1631
+ user.name
1632
+ ?? user.nick
1633
+ ?? user.username
1634
+ ?? rawItem.name
1635
+ ?? rawItem.nickname
1636
+ ?? rawItem.remark
1637
+ ?? '',
1638
+ )
1626
1639
  const avatar = hasUsableAvatar(user.avatar) ? getString(user.avatar) : ''
1627
- const item: Record<string, unknown> = { ...getObject(raw) }
1640
+ const item: Record<string, unknown> = { ...rawItem }
1628
1641
  if (!item.channel_id && item.channelId) item.channel_id = item.channelId
1629
1642
  if (!item.guild_id && item.guildId) item.guild_id = item.guildId
1630
1643
  if (getString(item.guild_id) || getString(item.guildId)) item._groupMember = true
@@ -1698,7 +1711,101 @@ function buildGroupItem(
1698
1711
  return item
1699
1712
  }
1700
1713
 
1701
- // 刷新群组时逐个请求 guild.get / channel.get,并把结果写回缓存和界面
1714
+ // 刷新联系人时只替换平台返回的 id/name/avatar,保留消息 session 抄录字段
1715
+ function applyFriendIdentity(raw: unknown, data: unknown): Record<string, unknown> {
1716
+ const cached = getObject(raw)
1717
+ const item: Record<string, unknown> = JSON.parse(JSON.stringify(cached)) as Record<string, unknown>
1718
+ const rawObj = getObject(item.raw) || cached
1719
+ const root = getObject(data)
1720
+ const user = getObject(root.user) || root
1721
+ const id = getString(user.id)
1722
+ || getString(item.id)
1723
+ || getString(rawObj.user_id)
1724
+ || getString(rawObj.id)
1725
+ || ''
1726
+ const name = getString(user.name)
1727
+ || getString(user.nick)
1728
+ || getString(user.nickname)
1729
+ || getString(item.name)
1730
+ || getString(rawObj.name)
1731
+ || getString(rawObj.nickname)
1732
+ || getString(rawObj.remark)
1733
+ || id
1734
+ const avatar = hasUsableAvatar(user.avatar)
1735
+ ? getString(user.avatar)
1736
+ : hasUsableAvatar(item.avatar)
1737
+ ? getString(item.avatar)
1738
+ : hasUsableAvatar(rawObj.avatar)
1739
+ ? getString(rawObj.avatar)
1740
+ : ''
1741
+ item.id = id
1742
+ item.name = name
1743
+ if (avatar) item.avatar = avatar
1744
+ else if (item.avatar && !hasUsableAvatar(item.avatar)) delete item.avatar
1745
+
1746
+ const nextRaw: Record<string, unknown> = { ...rawObj }
1747
+ nextRaw.id = id
1748
+ nextRaw.user_id = id
1749
+ if (name) nextRaw.name = name
1750
+ if (avatar) nextRaw.avatar = avatar
1751
+ item.raw = nextRaw
1752
+ return item
1753
+ }
1754
+
1755
+ // 刷新群组时只替换平台返回的 id/name/avatar,保留频道/群 ID 等 session 字段
1756
+ function applyGroupIdentity(
1757
+ raw: unknown,
1758
+ data: unknown,
1759
+ platform: string,
1760
+ ): Record<string, unknown> {
1761
+ const cached = getObject(raw)
1762
+ const item: Record<string, unknown> = JSON.parse(JSON.stringify(cached)) as Record<string, unknown>
1763
+ const rawObj = getObject(item.raw) || cached
1764
+ const root = getObject(data)
1765
+ const channel = getObject(root.channel) || root
1766
+ const guild = getObject(root.guild)
1767
+ const id = normalizeGroupId(
1768
+ getString(guild.id)
1769
+ || getString(channel.id)
1770
+ || getString(item.id)
1771
+ || getString(rawObj.group_id)
1772
+ || getString(rawObj.id)
1773
+ || getString(rawObj.guild_id)
1774
+ || '',
1775
+ )
1776
+ const name = getString(guild.name)
1777
+ || getString(channel.name)
1778
+ || getString(item.name)
1779
+ || getString(rawObj.group_name)
1780
+ || getString(rawObj.name)
1781
+ || id
1782
+ const avatar = hasUsableAvatar(guild.avatar)
1783
+ ? getString(guild.avatar)
1784
+ : hasUsableAvatar(channel.avatar)
1785
+ ? getString(channel.avatar)
1786
+ : hasUsableAvatar(item.avatar)
1787
+ ? getString(item.avatar)
1788
+ : hasUsableAvatar(rawObj.avatar)
1789
+ ? getString(rawObj.avatar)
1790
+ : buildQqGroupAvatar(platform, id)
1791
+ item.id = id
1792
+ item.name = name
1793
+ if (avatar) item.avatar = avatar
1794
+ else if (item.avatar && !hasUsableAvatar(item.avatar)) delete item.avatar
1795
+
1796
+ const nextRaw: Record<string, unknown> = { ...rawObj }
1797
+ nextRaw.id = id
1798
+ nextRaw.group_id = id
1799
+ if (name) {
1800
+ nextRaw.name = name
1801
+ nextRaw.group_name = name
1802
+ }
1803
+ if (avatar) nextRaw.avatar = avatar
1804
+ item.raw = nextRaw
1805
+ return item
1806
+ }
1807
+
1808
+ // 刷新群组时逐个请求 channel.get,并把结果写回缓存和界面
1702
1809
  async function fetchGroupProfiles(
1703
1810
  active: { platform: string; selfId: string },
1704
1811
  groups: unknown[],
@@ -1713,30 +1820,32 @@ async function fetchGroupProfiles(
1713
1820
  const rawObj = getObject(raw)
1714
1821
  const guildId = getString(rawObj.guild_id) || getString(rawObj.guildId) || id
1715
1822
  const channelId = getString(rawObj.channel_id) || getString(rawObj.channelId) || id
1716
- const [guildData, channelData] = await Promise.all([
1717
- withTimeout(
1718
- request('guild.get', { guild_id: guildId }, active).catch(() => null),
1719
- 5000,
1720
- ),
1721
- withTimeout(
1722
- request('channel.get', { channel_id: channelId, guild_id: guildId }, active).catch(() => null),
1723
- 5000,
1724
- ),
1725
- ])
1726
- const item = buildGroupItem(raw, { guild: guildData, channel: channelData }, active.platform)
1823
+ const channelData = await withTimeout(
1824
+ request('channel.get', { channel_id: channelId, guild_id: guildId }, active).catch(() => null),
1825
+ 5000,
1826
+ )
1827
+ const item = applyGroupIdentity(raw, channelData ?? {}, active.platform)
1727
1828
  processedItems.push(item)
1829
+ const itemRaw = getObject(item.raw) ?? item
1830
+ identityInfoCache.set(identityCacheKey('group', active.platform, active.selfId, id), {
1831
+ name: getString(item.name),
1832
+ avatar: hasUsableAvatar(item.avatar) ? getString(item.avatar) : '',
1833
+ raw: itemRaw,
1834
+ })
1728
1835
 
1729
1836
  if (updateUi && contactStore) {
1730
1837
  const existing = contactStore.userList.find((contact) => {
1731
1838
  return String(contact.group_id) === id
1732
1839
  })
1733
1840
  if (existing) {
1734
- if (item.group_name) existing.group_name = String(item.group_name)
1735
- if (item.avatar) existing.avatar = String(item.avatar)
1841
+ if (getString(itemRaw.group_name) || getString(itemRaw.name)) {
1842
+ existing.group_name = getString(itemRaw.group_name) || getString(itemRaw.name)
1843
+ }
1844
+ if (hasUsableAvatar(item.avatar)) existing.avatar = getString(item.avatar)
1736
1845
  else if (existing.avatar && !hasUsableAvatar(existing.avatar)) delete existing.avatar
1737
1846
  contactStore.userList = [...contactStore.userList]
1738
1847
  } else {
1739
- dispatch({ retcode: 0, data: [item] }, 'getGroupList')
1848
+ dispatch({ retcode: 0, data: [itemRaw] }, 'getGroupList')
1740
1849
  }
1741
1850
  }
1742
1851
 
@@ -1772,8 +1881,47 @@ async function cacheBotContacts(_bot: { platform: string; selfId: string }) {
1772
1881
  }
1773
1882
 
1774
1883
  export async function refreshAllBots() {
1775
- useContactStore().botStates.clear()
1776
- await loadContactsFromCache()
1884
+ const contactStore = useContactStore()
1885
+ try {
1886
+ const result = await requestAllBotsCache()
1887
+ const bots = Array.isArray(getObject(result).bots) ? getObject(result).bots as unknown[] : []
1888
+ for (const rawBot of bots) {
1889
+ const bot = getObject(rawBot)
1890
+ const platform = getString(bot.platform)
1891
+ const selfId = getString(bot.selfId)
1892
+ if (!platform || !selfId) continue
1893
+ const active = { platform, selfId }
1894
+ try {
1895
+ const groups = Array.isArray(bot.groups) ? bot.groups as unknown[] : []
1896
+ const friends = Array.isArray(bot.friends) ? bot.friends as unknown[] : []
1897
+ const groupItems = await fetchGroupProfiles(active, groups, false)
1898
+ const friendItems = await fetchFriendProfiles(active, friends, false)
1899
+ const groupRaws = groupItems.map((item) => getObject(item).raw ?? item)
1900
+ const friendRaws = friendItems.map((item) => cachedFriendRaw(getObject(item), item))
1901
+ const userList = [...groupRaws, ...friendRaws] as unknown as (UserFriendElem & UserGroupElem)[]
1902
+ const key = botKey(platform, selfId)
1903
+ const state = contactStore.botStates.get(key)
1904
+ contactStore.botStates.set(key, {
1905
+ userList,
1906
+ baseList: state?.baseList ?? [],
1907
+ onMsgList: state?.onMsgList ?? [],
1908
+ })
1909
+ if (isActiveBot(active)) {
1910
+ contactStore.userList = userList
1911
+ if (groupRaws.length > 0) {
1912
+ dispatch({ retcode: 0, data: groupRaws }, 'getGroupList')
1913
+ }
1914
+ if (friendRaws.length > 0) {
1915
+ dispatch({ retcode: 0, data: friendRaws }, 'getFriendList')
1916
+ }
1917
+ }
1918
+ } catch (error: unknown) {
1919
+ logger.add(LogType.ERR, `刷新机器人 ${platform}/${selfId} 联系人失败: ${String(error)}`)
1920
+ }
1921
+ }
1922
+ } catch (error: unknown) {
1923
+ logger.add(LogType.ERR, `刷新联系人失败: ${String(error)}`)
1924
+ }
1777
1925
  }
1778
1926
 
1779
1927
  async function fetchFriendProfiles(
@@ -1792,10 +1940,10 @@ async function fetchFriendProfiles(
1792
1940
  let data: unknown = {}
1793
1941
  if (!userGetBlocked) {
1794
1942
  try {
1795
- data = await Promise.race([
1796
- request('user.get', { user_id: id }, active),
1797
- new Promise<null>((resolve) => setTimeout(() => resolve(null), 5000)),
1798
- ]) ?? {}
1943
+ data = await withTimeout(
1944
+ request('user.get', { user_id: id }, active).catch(() => null),
1945
+ 5000,
1946
+ ) ?? {}
1799
1947
  } catch (error: unknown) {
1800
1948
  const message = error instanceof Error ? error.message : String(error)
1801
1949
  if (/is not a function|Satori API 返回 500/i.test(message)) {
@@ -1814,21 +1962,28 @@ async function fetchFriendProfiles(
1814
1962
  }
1815
1963
  }
1816
1964
  }
1817
- const item = buildFriendItem(raw, data)
1965
+ const item = applyFriendIdentity(raw, data)
1818
1966
  processedItems.push(item)
1967
+ const itemRaw = getObject(item.raw) ?? item
1968
+ identityInfoCache.set(identityCacheKey('user', active.platform, active.selfId, id), {
1969
+ name: getString(item.name),
1970
+ avatar: hasUsableAvatar(item.avatar) ? getString(item.avatar) : '',
1971
+ raw: itemRaw,
1972
+ })
1819
1973
  if (updateUi && contactStore) {
1820
1974
  const existing = contactStore.userList.find((contact) => {
1821
1975
  return String(contact.user_id) === id
1822
1976
  })
1823
1977
  if (existing) {
1824
- if (item.nickname) existing.nickname = String(item.nickname)
1825
- if (item.remark) existing.remark = String(item.remark)
1826
- if (item.avatar) existing.avatar = String(item.avatar)
1827
- if (item.class_id !== undefined) existing.class_id = Number(item.class_id)
1828
- if (item.class_name) existing.class_name = String(item.class_name)
1978
+ const displayName = getString(item.name)
1979
+ if (displayName) {
1980
+ existing.nickname = displayName
1981
+ existing.remark = displayName
1982
+ }
1983
+ if (hasUsableAvatar(item.avatar)) existing.avatar = getString(item.avatar)
1829
1984
  contactStore.userList = [...contactStore.userList]
1830
1985
  } else {
1831
- dispatch({ retcode: 0, data: [item] }, 'getFriendList')
1986
+ dispatch({ retcode: 0, data: [cachedFriendRaw(item, item)] }, 'getFriendList')
1832
1987
  contactStore.friendLoadedCount += 1
1833
1988
  }
1834
1989
  }
@@ -2090,12 +2245,17 @@ export async function loadContactsFromCache() {
2090
2245
  function startSatori() {
2091
2246
  disposeConnection?.()
2092
2247
  login.creating = true
2093
- disposeConnection = connectSatori(onSatoriEvent, onSatoriReady, (online) => {
2248
+ disposeConnection = connectBackend(onSatoriEvent, onSatoriReady, (online) => {
2094
2249
  login.status = online
2095
2250
  login.creating = !online
2096
2251
  })
2097
2252
  }
2098
2253
 
2254
+ // Satori 部分操作只返回纯文本成功标识(例如 OK),不需要进入 OneBot 风格事件分发
2255
+ function isPlainSuccessText(data: unknown): boolean {
2256
+ return typeof data === 'string' && !data.trim().startsWith('{') && !data.trim().startsWith('[')
2257
+ }
2258
+
2099
2259
  export class Connector {
2100
2260
  static create(address: string, token: string, _wss?: boolean) {
2101
2261
  login.address = address || 'Satori'
@@ -2172,6 +2332,7 @@ export class Connector {
2172
2332
  getString(mapped.params.channel_id) || getString(value.channel_id),
2173
2333
  )
2174
2334
  .then((result) => {
2335
+ if (isPlainSuccessText(result.data)) return
2175
2336
  const response = satoriResponseToOneBot(action, result.data, active.platform)
2176
2337
  response.channel_id = result.channelId
2177
2338
  response.channelId = result.channelId
@@ -2189,6 +2350,7 @@ export class Connector {
2189
2350
  }
2190
2351
  void request(mapped.method, mapped.params, active)
2191
2352
  .then((data) => {
2353
+ if (isPlainSuccessText(data)) return
2192
2354
  const response = satoriResponseToOneBot(action, data, active.platform)
2193
2355
  dispatch(response, echo)
2194
2356
  })
@@ -2535,12 +2535,14 @@ function newMsg(_: string, data: any) {
2535
2535
 
2536
2536
  const isTempGroupMessage = data.sub_type === 'group'
2537
2537
  const groupNoticeType = settingsStore.sysConfig.group_notice_type
2538
+ const privateNoticeType = settingsStore.sysConfig.private_notice_type ?? 'none'
2538
2539
  const hasForcedGroupInnerNotice = data.atme || data.atall || isImportant || isGroupNotice
2539
- const allowGroupInnerNotice = !isGroupMessage ||
2540
- groupNoticeType !== 'none' ||
2541
- hasForcedGroupInnerNotice
2542
- const allowGroupSystemNotice = !isGroupMessage ||
2543
- groupNoticeType === 'all'
2540
+ const allowGroupInnerNotice = isGroupMessage
2541
+ ? groupNoticeType !== 'none' || hasForcedGroupInnerNotice
2542
+ : privateNoticeType !== 'none'
2543
+ const allowGroupSystemNotice = isGroupMessage
2544
+ ? groupNoticeType === 'all'
2545
+ : privateNoticeType === 'all'
2544
2546
 
2545
2547
  // 会话状态更新 ============================================
2546
2548
  const sessionId = isTempGroupMessage ? sender : id
@@ -100,6 +100,7 @@ export const optDefault: { [key: string]: any } = {
100
100
  msg_taill: '',
101
101
  quick_send: 'default',
102
102
  group_notice_type: 'none',
103
+ private_notice_type: 'none',
103
104
  send_face: false,
104
105
  send_key: 'none',
105
106
  close_ga: false,
@@ -113,7 +114,6 @@ export const optDefault: { [key: string]: any } = {
113
114
  // Dev
114
115
  msg_type: 2,
115
116
  log_level: 'err',
116
- debug_msg: false,
117
117
  custom_css: '',
118
118
  // Glagame
119
119
  openai_api: '',
@@ -4,6 +4,7 @@ export interface SatoriBootstrap {
4
4
  endpoint: string
5
5
  token: string
6
6
  basePath: string
7
+ logins?: SatoriLogin[]
7
8
  blockedPlatforms: Array<{
8
9
  platformName: string
9
10
  exactMatch: boolean
@@ -115,10 +116,16 @@ function receiveBootstrap(data: Record<string, unknown>) {
115
116
  endpoint: getString(payload.endpoint) || '/satori',
116
117
  token: getString(payload.token),
117
118
  basePath: getString(payload.basePath) || '/chat-patch',
119
+ logins: Array.isArray(payload.logins)
120
+ ? payload.logins as SatoriLogin[]
121
+ : [],
118
122
  blockedPlatforms: Array.isArray(payload.blockedPlatforms)
119
123
  ? payload.blockedPlatforms as SatoriBootstrap['blockedPlatforms']
120
124
  : [],
121
125
  }
126
+ if (Array.isArray(payload.logins)) {
127
+ logins = payload.logins as SatoriLogin[]
128
+ }
122
129
  window.clearTimeout(bootstrapTimeout)
123
130
  resolveBootstrapCallback?.(bootstrap)
124
131
  resolveBootstrapCallback = null
@@ -225,6 +232,63 @@ export function handleConsoleResponse(data: Record<string, unknown>) {
225
232
  }
226
233
  }
227
234
 
235
+ export function connectBackend(
236
+ onEvent: (event: SatoriEvent) => void,
237
+ onReady: (logins: SatoriLogin[]) => void,
238
+ onStatus: (online: boolean) => void,
239
+ ): () => void {
240
+ let disposed = false
241
+
242
+ const handleMessage = (event: MessageEvent) => {
243
+ const data = getObject(event.data)
244
+ if (getString(data.source) !== 'chat-patch-event') return
245
+ const payload = getObject(data.payload)
246
+ const kind = getString(payload.kind)
247
+ if (kind === 'ready') {
248
+ const next = Array.isArray(payload.logins) ? payload.logins as SatoriLogin[] : []
249
+ logins = next
250
+ onReady(next)
251
+ onStatus(true)
252
+ return
253
+ }
254
+ if (kind === 'status') {
255
+ onStatus(payload.online === true)
256
+ return
257
+ }
258
+ if (kind !== 'event') return
259
+ const raw = getObject(payload.event)
260
+ sequence = Number(raw.sn ?? sequence)
261
+ localStorage.setItem('chat-patch:sn', String(sequence))
262
+ onEvent({
263
+ type: getString(raw.type),
264
+ platform: getString(raw.platform),
265
+ selfId: getString(raw.selfId),
266
+ timestamp: Number(raw.timestamp ?? Date.now()),
267
+ sn: sequence,
268
+ body: getObject(raw.body),
269
+ })
270
+ }
271
+
272
+ window.addEventListener('message', handleMessage)
273
+
274
+ void resolveBootstrap().then((info) => {
275
+ if (disposed) return
276
+ const initial = info.logins ?? []
277
+ if (initial.length) {
278
+ logins = initial
279
+ onReady(initial)
280
+ onStatus(true)
281
+ }
282
+ }).catch(() => {
283
+ if (!disposed) onStatus(false)
284
+ })
285
+
286
+ return () => {
287
+ disposed = true
288
+ window.removeEventListener('message', handleMessage)
289
+ }
290
+ }
291
+
228
292
  export function connect(
229
293
  onEvent: (event: SatoriEvent) => void,
230
294
  onReady: (logins: SatoriLogin[]) => void,
@@ -498,10 +498,6 @@
498
498
  <div><font-awesome-icon :icon="['fas', 'at']" /></div>
499
499
  <a>{{ $t('提及') }}</a>
500
500
  </div>
501
- <div v-show="tags.menuDisplay.poke" @click="sendPoke(selectedMsg ? selectedMsg.sender.user_id : undefined)">
502
- <div><font-awesome-icon :icon="['fas', 'fa-hand-point-up']" /></div>
503
- <a>{{ $t('戳一戳') }}</a>
504
- </div>
505
501
  <div v-show="tags.menuDisplay.remove" @click="removeUser">
506
502
  <div><font-awesome-icon :icon="['fas', 'trash-can']" /></div>
507
503
  <a>{{ $t('移出群聊') }}</a>
@@ -282,8 +282,6 @@
282
282
  function backToRobots() {
283
283
  snapshotCurrentBot()
284
284
  activeBotId.value = ''
285
- chatStore.messageList = []
286
- chatStore.chatInfo.show.id = 0
287
285
  }
288
286
 
289
287
  function selectRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
@@ -408,7 +406,7 @@
408
406
  }
409
407
  isSearch.value = false
410
408
  searchInfo.value = ''
411
- contactStore.showList = [] as any[]
409
+ contactStore.showList = []
412
410
 
413
411
  const back = {
414
412
  type: data.group_id ? 'group' : 'user',
@@ -449,19 +447,19 @@
449
447
  isSearch.value = true
450
448
  contactStore.showList = allContacts.value.filter(
451
449
  (item: ContactWithBot) => {
452
- const name = (
453
- item.user_id? item.nickname + item.remark: item.group_name
454
- ).toLowerCase()
450
+ const name = String(getShowName(item) ?? '').toLowerCase()
455
451
  if (name.includes(value)) return true
456
- const id = item.user_id? item.user_id: item.group_id
457
- if (id.toString() === value) return true
452
+ const id = String(item.user_id ?? item.group_id ?? '')
453
+ if (id.includes(value)) return true
454
+ const channelId = String(item.channel_id ?? item.channelId ?? '')
455
+ if (channelId && channelId.includes(value)) return true
458
456
  if (item.py_name && matchPinyin(item.py_name, value)) return true
459
457
  return false
460
458
  },
461
459
  )
462
460
  } else {
463
461
  isSearch.value = false
464
- contactStore.showList = [] as any[]
462
+ contactStore.showList = []
465
463
  }
466
464
  // macOS: 刷新 TouchBar
467
465
  if(backend.isDesktop()) {
@@ -258,8 +258,6 @@
258
258
  function backToRobots() {
259
259
  snapshotCurrentBot()
260
260
  activeBotId.value = ''
261
- chatStore.messageList = []
262
- chatStore.chatInfo.show.id = 0
263
261
  }
264
262
 
265
263
  function snapshotCurrentBot() {
@@ -348,6 +346,8 @@
348
346
  }
349
347
 
350
348
  function toggleRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
349
+ // 展开/折叠前清掉残留弹窗遮罩,避免点击被遮挡
350
+ uiStore.popBoxList = []
351
351
  if (activeBotId.value === botKey(bot.platform, bot.selfId)) {
352
352
  backToRobots()
353
353
  } else {
@@ -28,23 +28,6 @@
28
28
  </select>
29
29
  </div>
30
30
  </div>
31
- <div class="opt-item">
32
- <div :class="checkDefault('debug_msg')" />
33
- <font-awesome-icon :icon="['fas', 'robot']" />
34
- <div>
35
- <label for="opt-dev-debug-msg">{{ $t('禁用消息渲染') }}</label>
36
- <span>
37
- <a style="cursor: pointer" @click="sendAbab">{{ $t('点击进行 CAPTCHA 验证') }}</a>
38
- </span>
39
- </div>
40
- <label class="ss-switch">
41
- <input id="opt-dev-debug-msg" v-model="settingsStore.sysConfig.debug_msg"
42
- type="checkbox" name="debug_msg" @change="save">
43
- <div>
44
- <div />
45
- </div>
46
- </label>
47
- </div>
48
31
  <div class="opt-item">
49
32
  <font-awesome-icon :icon="['fas', 'rotate-left']" />
50
33
  <div>
@@ -55,7 +38,7 @@
55
38
  class="ss-button"
56
39
  style="width: 100px; font-size: 0.8rem; background: var(--color-red); border-color: var(--color-red); color: #fff"
57
40
  @click="resetDefaults">
58
- {{ $t('恢复') }}
41
+ {{ $t('重置') }}
59
42
  </button>
60
43
  </div>
61
44
  <div class="opt-item">
@@ -234,13 +217,6 @@ import packageInfo from '../../../package.json'
234
217
  })
235
218
  }
236
219
 
237
- function sendAbab() {
238
- new PopInfo().add(
239
- PopType.INFO,
240
- $t('你不是人(逃'),
241
- )
242
- }
243
-
244
220
  function printRuntime() {
245
221
  if(backend.isMobile()) {
246
222
  const switcher = document.getElementById('__vconsole')?.getElementsByClassName('vc-switch')[0]
@@ -66,6 +66,29 @@
66
66
  </select>
67
67
  </div>
68
68
  </div>
69
+ <div class="opt-item">
70
+ <div :class="checkDefault('private_notice_type')" />
71
+ <font-awesome-icon :icon="['fas', 'envelope']" />
72
+ <div>
73
+ <label for="opt-function-private-notice-type">{{ $t('私信消息通知方式') }}</label>
74
+ <span>{{ $t('默认不发起系统通知') }}</span>
75
+ </div>
76
+ <div class="select-wrapper">
77
+ <select id="opt-function-private-notice-type"
78
+ v-model="settingsStore.sysConfig.private_notice_type"
79
+ name="private_notice_type" title="private_notice_type" @change="save">
80
+ <option value="none">
81
+ {{ $t('不通知(默认)') }}
82
+ </option>
83
+ <option value="inner">
84
+ {{ $t('仅应用内通知') }}
85
+ </option>
86
+ <option value="all">
87
+ {{ $t('应用内通知和系统通知') }}
88
+ </option>
89
+ </select>
90
+ </div>
91
+ </div>
69
92
  </div>
70
93
  <div class="ss-card">
71
94
  <header>{{ $t('聊天选项') }}</header>
@@ -1 +1 @@
1
- {"root":["./src/main.ts","./src/function/base.ts","./src/function/connect.ts","./src/function/favicon.ts","./src/function/input.ts","./src/function/msg.ts","./src/function/notify.ts","./src/function/option.ts","./src/function/satori-model.ts","./src/function/satori.ts","./src/function/sender.ts","./src/function/tooltip.ts","./src/function/elements/information.ts","./src/function/elements/system.ts","./src/function/elements/vuecomp.ts","./src/function/model/emoji.ts","./src/function/model/img.ts","./src/function/model/msg-body.ts","./src/function/utils/apputil.ts","./src/function/utils/avatarutil.ts","./src/function/utils/backgroundutil.ts","./src/function/utils/linkviewutil.ts","./src/function/utils/localhistoryutil.ts","./src/function/utils/msgutil.ts","./src/function/utils/pinyin.ts","./src/function/utils/recordutil.ts","./src/function/utils/sessionutil.ts","./src/function/utils/systemutil.ts","./src/runtime/backend.ts","./src/state/auth.ts","./src/state/chat.ts","./src/state/connection.ts","./src/state/contact.ts","./src/state/qzone.ts","./src/state/sessionhistory.ts","./src/state/settings.ts","./src/state/sticker.ts","./src/state/ui.ts","./env.d.ts"],"errors":true,"version":"5.9.3"}
1
+ {"root":["./src/main.ts","./src/function/base.ts","./src/function/connect.ts","./src/function/debug.ts","./src/function/favicon.ts","./src/function/input.ts","./src/function/msg.ts","./src/function/notify.ts","./src/function/option.ts","./src/function/satori-model.ts","./src/function/satori.ts","./src/function/sender.ts","./src/function/tooltip.ts","./src/function/elements/information.ts","./src/function/elements/system.ts","./src/function/elements/vuecomp.ts","./src/function/model/emoji.ts","./src/function/model/img.ts","./src/function/model/msg-body.ts","./src/function/utils/apputil.ts","./src/function/utils/avatarutil.ts","./src/function/utils/backgroundutil.ts","./src/function/utils/linkviewutil.ts","./src/function/utils/localhistoryutil.ts","./src/function/utils/msgutil.ts","./src/function/utils/pinyin.ts","./src/function/utils/recordutil.ts","./src/function/utils/sessionutil.ts","./src/function/utils/systemutil.ts","./src/runtime/backend.ts","./src/state/auth.ts","./src/state/chat.ts","./src/state/connection.ts","./src/state/contact.ts","./src/state/qzone.ts","./src/state/sessionhistory.ts","./src/state/settings.ts","./src/state/sticker.ts","./src/state/ui.ts","./env.d.ts"],"errors":true,"version":"5.9.3"}