koishi-plugin-chat-patch 5.1.0 → 5.3.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.
- package/client/web/dist/assets/Chat-Bo5JdcNg.js +28 -0
- package/client/web/dist/assets/{Chat-CTdyeaX2.css → Chat-C-rmzMIw.css} +1 -1
- package/client/web/dist/assets/{MsgBody-DLeOklIb.js → MsgBody-h4C1nnBQ.js} +1 -1
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-a902ps2r.js +67 -0
- package/client/web/dist/assets/{index-Cz89G4we.js → index-CdTmWcO4.js} +121 -129
- package/client/web/dist/assets/index-wjp_A4n1.css +1 -0
- package/client/web/dist/assets/pinyin-Dsq98249.js +157657 -0
- package/client/web/dist/index.html +2 -2
- package/client/web/dist/notice_local.json +1 -0
- package/client/web/package-lock.json +49 -0
- package/client/web/package.json +1 -0
- package/client/web/public/notice_local.json +1 -0
- package/client/web/src/components/AboutPan.vue +2 -1
- package/client/web/src/components/BulletinBody.vue +1 -1
- package/client/web/src/components/EmojiFace.vue +0 -6
- package/client/web/src/components/FacePan.vue +0 -12
- package/client/web/src/components/FriendBody.vue +1 -0
- package/client/web/src/components/MsgBody.vue +4 -4
- package/client/web/src/components/SettingsTab.vue +133 -0
- package/client/web/src/components/TinySessionBody.vue +1 -0
- package/client/web/src/components/UpdatePan.vue +2 -2
- package/client/web/src/components/tooltip/UserInfoTooltip.vue +1 -1
- package/client/web/src/function/connect.ts +81 -28
- package/client/web/src/function/model/emoji.ts +7 -67
- package/client/web/src/function/msg.ts +68 -26
- package/client/web/src/function/satori-model.ts +8 -0
- package/client/web/src/function/satori.ts +31 -1
- package/client/web/src/function/utils/appUtil.ts +1 -4
- package/client/web/src/function/utils/avatarUtil.ts +18 -3
- package/client/web/src/function/utils/msgUtil.ts +86 -52
- package/client/web/src/function/utils/pinyin.ts +25 -65
- package/client/web/src/function/utils/sessionUtil.ts +90 -14
- package/client/web/src/main.ts +5 -0
- package/client/web/src/pages/Chat.vue +3 -4
- package/client/web/src/pages/Friends.vue +72 -30
- package/client/web/src/pages/Info.vue +3 -3
- package/client/web/src/pages/Messages.vue +59 -26
- package/client/web/src/pages/Options.vue +4 -4
- package/client/web/src/pages/Qzone.vue +2 -2
- package/client/web/src/pages/chat-view/SystemNotice.vue +3 -3
- package/client/web/src/pages/options/OptAccount.vue +6 -4
- package/client/web/src/pages/options/OptDev.vue +1 -14
- package/lib/index.js +14 -14
- package/lib/types.d.ts +115 -0
- package/package.json +1 -1
- package/src/bootstrap.ts +1 -0
- package/src/config.ts +1 -1
- package/src/recorder.ts +7 -7
- package/src/types.ts +4 -0
- package/src/web.ts +8 -9
- package/client/web/dist/assets/Chat-BoNiWGxb.js +0 -28
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CGjy4Jzz.js +0 -67
- package/client/web/dist/assets/index-BgOX_tNa.css +0 -1
|
@@ -5,30 +5,25 @@ export type PinYinData = {
|
|
|
5
5
|
short: string[]
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const PINYIN_SCRIPT_SRC = 'https://lib.stapxs.cn/modules/pinyin.min.js'
|
|
8
|
+
type PinyinModule = typeof import('pinyin')
|
|
11
9
|
|
|
10
|
+
let pinyinModule: PinyinModule | null = null
|
|
12
11
|
let pinyinLoadPromise: Promise<boolean> | null = null
|
|
13
12
|
|
|
14
13
|
function createEmptyPinyinData(): PinYinData {
|
|
15
14
|
return {
|
|
16
15
|
main: [],
|
|
17
|
-
short: []
|
|
16
|
+
short: [],
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
function hasPinyinLib() {
|
|
22
|
-
return typeof window !== 'undefined' && typeof window.pinyin !== 'undefined'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
function scheduleIdleTask(task: () => void) {
|
|
26
21
|
if (typeof window === 'undefined') return
|
|
27
22
|
|
|
28
23
|
const idleWindow = window as Window & {
|
|
29
24
|
requestIdleCallback?: (
|
|
30
25
|
callback: IdleRequestCallback,
|
|
31
|
-
options?: IdleRequestOptions
|
|
26
|
+
options?: IdleRequestOptions,
|
|
32
27
|
) => number
|
|
33
28
|
}
|
|
34
29
|
|
|
@@ -41,57 +36,23 @@ function scheduleIdleTask(task: () => void) {
|
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
export function isPinyinReady() {
|
|
44
|
-
return
|
|
39
|
+
return pinyinModule !== null
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
export function ensurePinyinLoaded(): Promise<boolean> {
|
|
48
|
-
if (
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
pinyinLoadPromise = null
|
|
62
|
-
}
|
|
63
|
-
resolve(success)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const handleLoad = () => {
|
|
67
|
-
if (script) {
|
|
68
|
-
script.dataset.loaded = 'true'
|
|
69
|
-
}
|
|
70
|
-
finish(hasPinyinLib())
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const handleError = () => {
|
|
74
|
-
if (isDebugMode()) console.warn('拼音库加载失败')
|
|
75
|
-
script?.remove()
|
|
76
|
-
finish(false)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (script?.dataset.loaded === 'true') {
|
|
80
|
-
finish(hasPinyinLib())
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!script) {
|
|
85
|
-
script = document.createElement('script')
|
|
86
|
-
script.src = PINYIN_SCRIPT_SRC
|
|
87
|
-
script.async = true
|
|
88
|
-
script.dataset.ssqqPinyinLib = 'true'
|
|
89
|
-
document.body.appendChild(script)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
script.addEventListener('load', handleLoad, { once: true })
|
|
93
|
-
script.addEventListener('error', handleError, { once: true })
|
|
94
|
-
})
|
|
43
|
+
if (pinyinModule) return Promise.resolve(true)
|
|
44
|
+
if (pinyinLoadPromise) return pinyinLoadPromise
|
|
45
|
+
|
|
46
|
+
pinyinLoadPromise = import('pinyin')
|
|
47
|
+
.then((mod) => {
|
|
48
|
+
pinyinModule = mod
|
|
49
|
+
return true
|
|
50
|
+
})
|
|
51
|
+
.catch((error: unknown) => {
|
|
52
|
+
if (isDebugMode()) console.warn('本地拼音库加载失败:', error)
|
|
53
|
+
pinyinLoadPromise = null
|
|
54
|
+
return false
|
|
55
|
+
})
|
|
95
56
|
|
|
96
57
|
return pinyinLoadPromise
|
|
97
58
|
}
|
|
@@ -103,22 +64,21 @@ export function preloadPinyin() {
|
|
|
103
64
|
}
|
|
104
65
|
|
|
105
66
|
export function getPinyin(name: string): PinYinData {
|
|
106
|
-
if (!
|
|
67
|
+
if (!pinyinModule) return createEmptyPinyinData()
|
|
107
68
|
|
|
108
69
|
try {
|
|
109
|
-
const pinyinLib =
|
|
110
|
-
if (!pinyinLib) return createEmptyPinyinData()
|
|
70
|
+
const pinyinLib = pinyinModule.pinyin
|
|
111
71
|
return {
|
|
112
|
-
main: pinyinLib
|
|
72
|
+
main: pinyinLib(name, {
|
|
113
73
|
heteronym: true,
|
|
114
74
|
compact: true,
|
|
115
75
|
style: 'normal',
|
|
116
|
-
}).map((item
|
|
117
|
-
short: pinyinLib
|
|
76
|
+
}).map((item) => item.join('').toLowerCase()),
|
|
77
|
+
short: pinyinLib(name, {
|
|
118
78
|
heteronym: true,
|
|
119
79
|
compact: true,
|
|
120
80
|
style: 'first_letter',
|
|
121
|
-
}).map((item
|
|
81
|
+
}).map((item) => item.join('').toLowerCase()),
|
|
122
82
|
}
|
|
123
83
|
} catch (error) {
|
|
124
84
|
if (isDebugMode()) console.warn('拼音转换失败:', error)
|
|
@@ -128,7 +88,7 @@ export function getPinyin(name: string): PinYinData {
|
|
|
128
88
|
|
|
129
89
|
export function matchPinyin(
|
|
130
90
|
pinyinData: PinYinData,
|
|
131
|
-
matchStr: string
|
|
91
|
+
matchStr: string,
|
|
132
92
|
): boolean {
|
|
133
93
|
const str = matchStr.toLowerCase()
|
|
134
94
|
for (const py of pinyinData.main) {
|
|
@@ -18,15 +18,45 @@ export function getSessionId(item: Session): number | string {
|
|
|
18
18
|
return item.user_id ?? item.group_id ?? 0
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function getLegacySessionId(item: Session): number | string | undefined {
|
|
22
|
+
return item.user_id ?? item.group_id
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getSessionAliases(item: Session): string[] {
|
|
26
|
+
const rawAliases: Array<number | string | undefined> = [
|
|
27
|
+
getSessionId(item),
|
|
28
|
+
getLegacySessionId(item),
|
|
29
|
+
]
|
|
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
|
+
const aliases = rawAliases.filter((value): value is number | string => {
|
|
37
|
+
const text = String(value ?? '')
|
|
38
|
+
return text !== '' && text !== '0'
|
|
39
|
+
})
|
|
40
|
+
return [...new Set(aliases.map((value) => normalizeSessionId(value)))]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getSessionDedupKey(item: Session): string {
|
|
44
|
+
const channelId = String(item.channel_id ?? item.channelId ?? '')
|
|
45
|
+
const groupId = String(item.group_id ?? '')
|
|
46
|
+
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)}`
|
|
52
|
+
}
|
|
53
|
+
|
|
21
54
|
export function setSessionContact(
|
|
22
55
|
map: Map<number | string, Session>,
|
|
23
56
|
item: Session,
|
|
24
57
|
) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const legacyId = item.user_id ?? item.group_id
|
|
28
|
-
if (legacyId !== undefined && legacyId !== null && String(legacyId) !== key) {
|
|
29
|
-
map.set(normalizeSessionId(String(legacyId)), item)
|
|
58
|
+
for (const alias of getSessionAliases(item)) {
|
|
59
|
+
map.set(alias, item)
|
|
30
60
|
}
|
|
31
61
|
}
|
|
32
62
|
|
|
@@ -34,8 +64,13 @@ export function findSessionContact(
|
|
|
34
64
|
contacts: Session[],
|
|
35
65
|
sessionId: number | string,
|
|
36
66
|
) {
|
|
67
|
+
const target = normalizeSessionId(sessionId)
|
|
37
68
|
return contacts.find((item) => {
|
|
38
|
-
return
|
|
69
|
+
return getSessionAliases(item).some((alias) => alias === target)
|
|
70
|
+
|| (
|
|
71
|
+
Boolean(item.group_id) &&
|
|
72
|
+
normalizeGroupId(String(item.group_id)) === normalizeGroupId(target)
|
|
73
|
+
)
|
|
39
74
|
})
|
|
40
75
|
}
|
|
41
76
|
|
|
@@ -98,6 +133,28 @@ function copyDefinedSessionState<K extends SessionStateKey>(
|
|
|
98
133
|
) {
|
|
99
134
|
const value = currentSession[key]
|
|
100
135
|
if (value !== undefined) {
|
|
136
|
+
const current = contact[key]
|
|
137
|
+
if (
|
|
138
|
+
key === 'time' &&
|
|
139
|
+
typeof current === 'number' &&
|
|
140
|
+
typeof value === 'number' &&
|
|
141
|
+
current > value
|
|
142
|
+
) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
contact[key] = value
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function copyMissingIdentity<K extends keyof Session>(
|
|
150
|
+
contact: Session,
|
|
151
|
+
currentSession: Session,
|
|
152
|
+
key: K,
|
|
153
|
+
) {
|
|
154
|
+
const value = currentSession[key]
|
|
155
|
+
if (value === undefined || value === null || String(value) === '') return
|
|
156
|
+
const current = contact[key]
|
|
157
|
+
if (current === undefined || current === null || String(current) === '') {
|
|
101
158
|
contact[key] = value
|
|
102
159
|
}
|
|
103
160
|
}
|
|
@@ -106,6 +163,21 @@ export function mergeSessionState(
|
|
|
106
163
|
contact: Session,
|
|
107
164
|
currentSession: Session,
|
|
108
165
|
) {
|
|
166
|
+
const identityKeys = [
|
|
167
|
+
'channel_id',
|
|
168
|
+
'channelId',
|
|
169
|
+
'guild_id',
|
|
170
|
+
'guildId',
|
|
171
|
+
'group_id',
|
|
172
|
+
'user_id',
|
|
173
|
+
'group_name',
|
|
174
|
+
'nickname',
|
|
175
|
+
'remark',
|
|
176
|
+
'avatar',
|
|
177
|
+
] as const
|
|
178
|
+
identityKeys.forEach((key) =>
|
|
179
|
+
copyMissingIdentity(contact, currentSession, key),
|
|
180
|
+
)
|
|
109
181
|
SESSION_STATE_KEYS.forEach((key) =>
|
|
110
182
|
copyDefinedSessionState(contact, currentSession, key),
|
|
111
183
|
)
|
|
@@ -122,14 +194,18 @@ export function mergeEarlySessionContacts(
|
|
|
122
194
|
) {
|
|
123
195
|
let didMerge = false
|
|
124
196
|
contacts.forEach((contact) => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
197
|
+
for (const [key, currentSession] of sessions.entries()) {
|
|
198
|
+
if (currentSession === contact) continue
|
|
199
|
+
if (
|
|
200
|
+
findSessionContact([currentSession], getSessionId(contact))
|
|
201
|
+
|| getSessionDedupKey(contact) === getSessionDedupKey(currentSession)
|
|
202
|
+
) {
|
|
203
|
+
sessions.set(
|
|
204
|
+
key,
|
|
205
|
+
mergeSessionState(contact, currentSession),
|
|
206
|
+
)
|
|
207
|
+
didMerge = true
|
|
208
|
+
}
|
|
133
209
|
}
|
|
134
210
|
})
|
|
135
211
|
return didMerge
|
package/client/web/src/main.ts
CHANGED
|
@@ -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
|
|
520
|
+
<div @click="consoleLogMsg">
|
|
521
521
|
<div><font-awesome-icon :icon="['fas', 'screwdriver-wrench']" /></div>
|
|
522
522
|
<a>{{ $t('调试信息') }}</a>
|
|
523
523
|
</div>
|
|
@@ -738,7 +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
741
|
let selfRefreshTimer: number | null = null
|
|
743
742
|
|
|
744
743
|
function ensureChannelPrefix(channelId: string, prefix: string): string {
|
|
@@ -50,23 +50,23 @@
|
|
|
50
50
|
{{ $t('空') }}
|
|
51
51
|
</div>
|
|
52
52
|
<FriendBody v-for="item in contactStore.showList"
|
|
53
|
-
:key="'search-' + item.
|
|
53
|
+
:key="'search-' + (item.channel_id || item.channelId || item.user_id || item.group_id)"
|
|
54
54
|
:data="item"
|
|
55
55
|
from="friend"
|
|
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('空') }}
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
return ( get.class_id == info.class_id )
|
|
101
101
|
},
|
|
102
102
|
)"
|
|
103
|
-
:key=" 'fb-' + (item.
|
|
103
|
+
:key=" 'fb-' + (item.channel_id || item.channelId || item.user_id || item.group_id) "
|
|
104
104
|
:data="item" from="friend"
|
|
105
105
|
@click="userClick(item, $event)" />
|
|
106
106
|
</div>
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
return get.class_id == undefined
|
|
126
126
|
},
|
|
127
127
|
)"
|
|
128
|
-
:key="'fb-' + (item.
|
|
128
|
+
:key="'fb-' + (item.channel_id || item.channelId || item.user_id || item.group_id)"
|
|
129
129
|
:data="item"
|
|
130
130
|
from="friend"
|
|
131
131
|
@click="userClick(item, $event)" />
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
</template>
|
|
135
135
|
<template v-else>
|
|
136
136
|
<FriendBody v-for="item in contactStore.userList"
|
|
137
|
-
:key="'fb-' + (item.
|
|
137
|
+
:key="'fb-' + (item.channel_id || item.channelId || item.user_id || item.group_id)"
|
|
138
138
|
:data="item"
|
|
139
139
|
from="friend"
|
|
140
140
|
@click="userClick(item, $event)" />
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
<div v-else class="list">
|
|
145
145
|
<div>
|
|
146
146
|
<FriendBody v-for="item in contactStore.showList"
|
|
147
|
-
:key="'fb-' + (item.
|
|
147
|
+
:key="'fb-' + (item.channel_id || item.channelId || item.user_id || item.group_id)"
|
|
148
148
|
:data="item" from="friend"
|
|
149
149
|
@click="userClick(item, $event)" />
|
|
150
150
|
</div>
|
|
@@ -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 { normalizeSessionId } from '../function/utils/sessionUtil'
|
|
186
|
+
import { botKey, getActiveBot, getLogins, setActiveBot } from '../function/satori'
|
|
187
|
+
import { normalizeGroupId, 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
|
|
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[]>(() => {
|
|
@@ -240,17 +245,27 @@
|
|
|
240
245
|
bot?: ContactWithBot['_bot'],
|
|
241
246
|
) => {
|
|
242
247
|
for (const item of items) {
|
|
243
|
-
const
|
|
248
|
+
const rawId = String(
|
|
249
|
+
item.channel_id
|
|
250
|
+
|| item.channelId
|
|
251
|
+
|| item.user_id
|
|
252
|
+
|| item.group_id
|
|
253
|
+
|| '',
|
|
254
|
+
)
|
|
255
|
+
const groupId = String(item.group_id || '')
|
|
256
|
+
const id = groupId
|
|
257
|
+
? `group:${normalizeGroupId(rawId || groupId)}`
|
|
258
|
+
: `user:${rawId.replace(/^(?:private|direct):/i, '')}`
|
|
244
259
|
const key = [bot?.platform ?? '', bot?.selfId ?? '', id]
|
|
245
260
|
.map((value) => encodeURIComponent(String(value)))
|
|
246
261
|
.join(':')
|
|
247
262
|
map.set(key, { ...item, _bot: bot })
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
|
-
const currentBot = satoriLogins.value.find((item) => item.selfId === activeBotId.value)
|
|
265
|
+
const currentBot = satoriLogins.value.find((item) => botKey(item.platform, item.selfId) === activeBotId.value)
|
|
251
266
|
addContacts(contactStore.userList, currentBot)
|
|
252
|
-
for (const [
|
|
253
|
-
const bot = satoriLogins.value.find((item) => item.selfId ===
|
|
267
|
+
for (const [stateKey, state] of contactStore.botStates) {
|
|
268
|
+
const bot = satoriLogins.value.find((item) => botKey(item.platform, item.selfId) === stateKey)
|
|
254
269
|
addContacts(state.userList, bot)
|
|
255
270
|
}
|
|
256
271
|
return [...map.values()]
|
|
@@ -274,9 +289,10 @@
|
|
|
274
289
|
}
|
|
275
290
|
|
|
276
291
|
function selectRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
|
|
277
|
-
|
|
292
|
+
const key = botKey(bot.platform, bot.selfId)
|
|
293
|
+
if (key === activeBotId.value) return
|
|
278
294
|
snapshotCurrentBot()
|
|
279
|
-
activeBotId.value =
|
|
295
|
+
activeBotId.value = key
|
|
280
296
|
setActiveBot(bot.platform, bot.selfId)
|
|
281
297
|
loginInfo.uin = bot.selfId
|
|
282
298
|
loginInfo.nickname = bot.name
|
|
@@ -289,14 +305,31 @@
|
|
|
289
305
|
platform: bot.platform,
|
|
290
306
|
avatar: bot.avatar,
|
|
291
307
|
satoriLogins: satoriLogins.value,
|
|
292
|
-
selectedSatoriBot:
|
|
308
|
+
selectedSatoriBot: key,
|
|
293
309
|
}
|
|
294
|
-
const saved = contactStore.botStates.get(
|
|
310
|
+
const saved = contactStore.botStates.get(key)
|
|
295
311
|
if (saved) {
|
|
296
312
|
contactStore.userList = saved.userList
|
|
297
313
|
contactStore.baseOnMsgList.clear()
|
|
298
|
-
for (const [
|
|
299
|
-
|
|
314
|
+
for (const [, value] of saved.baseList) {
|
|
315
|
+
const id = String(value.channel_id || value.channelId || value.user_id || value.group_id || '')
|
|
316
|
+
if (id && id !== '0') {
|
|
317
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(id), value)
|
|
318
|
+
const legacyId = String(value.user_id || value.group_id || '')
|
|
319
|
+
if (legacyId && legacyId !== id) {
|
|
320
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(legacyId), value)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const item of saved.onMsgList) {
|
|
325
|
+
const id = String(item.channel_id || item.channelId || item.user_id || item.group_id || '')
|
|
326
|
+
if (id && id !== '0') {
|
|
327
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(id), item)
|
|
328
|
+
const legacyId = String(item.user_id || item.group_id || '')
|
|
329
|
+
if (legacyId && legacyId !== id) {
|
|
330
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(legacyId), item)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
300
333
|
}
|
|
301
334
|
contactStore.onMsgList = saved.onMsgList
|
|
302
335
|
flushPendingBotEvents(bot.platform, bot.selfId)
|
|
@@ -312,7 +345,7 @@
|
|
|
312
345
|
}
|
|
313
346
|
|
|
314
347
|
function toggleRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
|
|
315
|
-
if (activeBotId.value === bot.selfId) {
|
|
348
|
+
if (activeBotId.value === botKey(bot.platform, bot.selfId)) {
|
|
316
349
|
backToRobots()
|
|
317
350
|
} else {
|
|
318
351
|
selectRobot(bot)
|
|
@@ -368,7 +401,7 @@
|
|
|
368
401
|
}
|
|
369
402
|
|
|
370
403
|
function userClick(data: ContactWithBot, event: Event) {
|
|
371
|
-
if (data._bot && data._bot.selfId !== activeBotId.value) {
|
|
404
|
+
if (data._bot && botKey(data._bot.platform, data._bot.selfId) !== activeBotId.value) {
|
|
372
405
|
selectRobot(data._bot)
|
|
373
406
|
}
|
|
374
407
|
const sender = event.currentTarget as HTMLDivElement
|
|
@@ -380,18 +413,27 @@
|
|
|
380
413
|
contactStore.showList = [] as any[]
|
|
381
414
|
|
|
382
415
|
const back = {
|
|
383
|
-
type: data.
|
|
384
|
-
id: data.
|
|
416
|
+
type: data.group_id ? 'group' : 'user',
|
|
417
|
+
id: data.group_id || data.user_id,
|
|
385
418
|
name: getShowName(data),
|
|
386
419
|
avatar: data.avatar || '/img/icons/icon.svg',
|
|
387
420
|
jump: sender.dataset.jump,
|
|
388
|
-
channel_id: data.channel_id,
|
|
421
|
+
channel_id: data.channel_id || data.channelId,
|
|
389
422
|
guild_id: data.guild_id,
|
|
390
423
|
} as BaseChatInfoElem
|
|
391
424
|
if (back.id === undefined || back.id === null || String(back.id) === '' || String(back.id) === '0') return
|
|
392
425
|
// 更新聊天框
|
|
393
426
|
emit('userClick', back)
|
|
394
|
-
|
|
427
|
+
const sessionId = String(
|
|
428
|
+
data.channel_id
|
|
429
|
+
|| data.channelId
|
|
430
|
+
|| data.user_id
|
|
431
|
+
|| data.group_id
|
|
432
|
+
|| '',
|
|
433
|
+
)
|
|
434
|
+
if (sessionId && sessionId !== '0') {
|
|
435
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(sessionId), data)
|
|
436
|
+
}
|
|
395
437
|
// 获取历史消息
|
|
396
438
|
if(!uiStore.nowGetHistory) {
|
|
397
439
|
emit('loadHistory', back)
|
|
@@ -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>
|