koishi-plugin-chat-patch 3.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/vue/index.vue +60 -634
- package/client/web/env.d.ts +56 -0
- package/client/web/index.html +21 -0
- package/client/web/package-lock.json +3419 -0
- package/client/web/package.json +62 -0
- package/client/web/public/bcui/css/color-dark.css +35 -0
- package/client/web/public/bcui/css/color-light.css +35 -0
- package/client/web/public/bcui/css/style.css +468 -0
- package/client/web/public/bcui/js/main.js +193 -0
- package/client/web/public/css/append-dark.css +13 -0
- package/client/web/public/css/append-light.css +13 -0
- package/client/web/public/css/append-napcat-dark.css +14 -0
- package/client/web/public/css/append-napcat-light.css +14 -0
- package/client/web/public/favicon.ico +0 -0
- package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
- package/client/web/public/img/icons/icon-maskable.png +0 -0
- package/client/web/public/img/icons/icon.png +0 -0
- package/client/web/public/img/icons/icon.svg +10 -0
- package/client/web/public/robots.txt +2 -0
- package/client/web/public/sw.js +58 -0
- package/client/web/scripts/copy-bcui.mjs +17 -0
- package/client/web/src/App.vue +1127 -0
- package/client/web/src/assets/css/append/append_darwin.css +68 -0
- package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
- package/client/web/src/assets/css/append/append_linux.css +125 -0
- package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
- package/client/web/src/assets/css/append/append_napcat.css +35 -0
- package/client/web/src/assets/css/append/append_new.css +843 -0
- package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
- package/client/web/src/assets/css/append/append_win32.css +109 -0
- package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
- package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
- package/client/web/src/assets/css/chat.css +2104 -0
- package/client/web/src/assets/css/msg.css +941 -0
- package/client/web/src/assets/css/options.css +811 -0
- package/client/web/src/assets/css/sys_notice.css +62 -0
- package/client/web/src/assets/css/view.css +2105 -0
- package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
- package/client/web/src/assets/img/Vue.png +0 -0
- package/client/web/src/assets/img/cakes.svg +189 -0
- package/client/web/src/assets/img/icons/AppIcon.png +0 -0
- package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
- package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
- package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
- package/client/web/src/assets/img/icons/exit.png +0 -0
- package/client/web/src/assets/img/icons/pen.png +0 -0
- package/client/web/src/assets/img/stars.svg +89 -0
- package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
- package/client/web/src/assets/l10n/_About I10n.md +35 -0
- package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
- package/client/web/src/assets/l10n/zh-CN.po +1941 -0
- package/client/web/src/assets/logo.png +0 -0
- package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
- package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
- package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
- package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
- package/client/web/src/assets/qed.txt +1 -0
- package/client/web/src/components/AboutPan.vue +197 -0
- package/client/web/src/components/BulletinBody.vue +129 -0
- package/client/web/src/components/DepPan.vue +310 -0
- package/client/web/src/components/EmojiFace.vue +32 -0
- package/client/web/src/components/FacePan.vue +461 -0
- package/client/web/src/components/FileBody.vue +129 -0
- package/client/web/src/components/FileManager.vue +532 -0
- package/client/web/src/components/FriendBody.vue +54 -0
- package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
- package/client/web/src/components/History.vue +49 -0
- package/client/web/src/components/LazyLottie.vue +82 -0
- package/client/web/src/components/MergePan.vue +127 -0
- package/client/web/src/components/MsgBody.vue +1478 -0
- package/client/web/src/components/MusicPlayer.vue +549 -0
- package/client/web/src/components/NoticeBody.vue +168 -0
- package/client/web/src/components/QzonePermissionPan.vue +150 -0
- package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
- package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
- package/client/web/src/components/TinySessionBody.vue +51 -0
- package/client/web/src/components/UmamiInfoPan.vue +1549 -0
- package/client/web/src/components/UpdatePan.vue +263 -0
- package/client/web/src/components/ViewerCom.vue +1360 -0
- package/client/web/src/components/VoiceMsg.vue +626 -0
- package/client/web/src/components/WelPan.vue +894 -0
- package/client/web/src/components/msg-body/.gitkeep +0 -0
- package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
- package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
- package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
- package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
- package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
- package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
- package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
- package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
- package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
- package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
- package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
- package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
- package/client/web/src/components/notice-component/.gitkeep +0 -0
- package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
- package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
- package/client/web/src/components/tooltip/Tooltip.vue +50 -0
- package/client/web/src/components/tooltip/Tooltips.vue +14 -0
- package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
- package/client/web/src/function/base.ts +238 -0
- package/client/web/src/function/connect.ts +2042 -0
- package/client/web/src/function/debug.ts +10 -0
- package/client/web/src/function/elements/information.ts +141 -0
- package/client/web/src/function/elements/system.ts +101 -0
- package/client/web/src/function/elements/vueComp.ts +62 -0
- package/client/web/src/function/favicon.ts +76 -0
- package/client/web/src/function/input.ts +69 -0
- package/client/web/src/function/model/emoji.ts +127 -0
- package/client/web/src/function/model/img.ts +150 -0
- package/client/web/src/function/model/msg-body.ts +51 -0
- package/client/web/src/function/msg.ts +2674 -0
- package/client/web/src/function/notify.ts +225 -0
- package/client/web/src/function/option.ts +749 -0
- package/client/web/src/function/satori-model.ts +818 -0
- package/client/web/src/function/satori.ts +325 -0
- package/client/web/src/function/sender.ts +178 -0
- package/client/web/src/function/tooltip.ts +49 -0
- package/client/web/src/function/utils/appUtil.ts +2288 -0
- package/client/web/src/function/utils/avatarUtil.ts +31 -0
- package/client/web/src/function/utils/backgroundUtil.ts +110 -0
- package/client/web/src/function/utils/linkViewUtil.ts +101 -0
- package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
- package/client/web/src/function/utils/msgUtil.ts +1242 -0
- package/client/web/src/function/utils/pinyin.ts +141 -0
- package/client/web/src/function/utils/recordUtil.ts +187 -0
- package/client/web/src/function/utils/sessionUtil.ts +114 -0
- package/client/web/src/function/utils/systemUtil.ts +664 -0
- package/client/web/src/main.ts +91 -0
- package/client/web/src/pages/Chat.vue +3453 -0
- package/client/web/src/pages/Friends.vue +636 -0
- package/client/web/src/pages/Info.vue +717 -0
- package/client/web/src/pages/Messages.vue +875 -0
- package/client/web/src/pages/Options.vue +82 -0
- package/client/web/src/pages/Qzone.vue +1178 -0
- package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
- package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
- package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
- package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
- package/client/web/src/pages/options/OptAccount.vue +107 -0
- package/client/web/src/pages/options/OptAddon.vue +185 -0
- package/client/web/src/pages/options/OptDev.vue +704 -0
- package/client/web/src/pages/options/OptFunction.vue +403 -0
- package/client/web/src/pages/options/OptInfo.vue +165 -0
- package/client/web/src/pages/options/OptView.vue +846 -0
- package/client/web/src/runtime/backend.ts +310 -0
- package/client/web/src/state/auth.ts +14 -0
- package/client/web/src/state/chat.ts +36 -0
- package/client/web/src/state/connection.ts +20 -0
- package/client/web/src/state/contact.ts +37 -0
- package/client/web/src/state/qzone.ts +18 -0
- package/client/web/src/state/sessionHistory.ts +25 -0
- package/client/web/src/state/settings.ts +18 -0
- package/client/web/src/state/sticker.ts +10 -0
- package/client/web/src/state/ui.ts +56 -0
- package/client/web/tsconfig.json +27 -0
- package/client/web/tsconfig.tsbuildinfo +1 -0
- package/client/web/vite.config.ts +33 -0
- package/dist/index.js +1 -12
- package/dist/style.css +1 -1
- package/lib/index.js +2070 -2077
- package/package.json +15 -2
- package/readme.md +0 -22
- package/src/bootstrap.ts +71 -0
- package/src/cache.ts +319 -0
- package/src/config.ts +23 -35
- package/src/database.ts +650 -0
- package/src/index.ts +27 -59
- package/src/media.ts +302 -0
- package/src/recorder.ts +175 -0
- package/src/self-message.ts +456 -0
- package/src/server.d.ts +26 -0
- package/src/types.ts +108 -48
- package/src/web.ts +712 -0
- package/client/vue/chat-logic.ts +0 -683
- package/client/vue/composables/useChatActions.ts +0 -61
- package/client/vue/composables/useChatData.ts +0 -185
- package/client/vue/composables/useImageCache.ts +0 -281
- package/client/vue/composables/useVideoCache.ts +0 -125
- package/client/vue/types.ts +0 -72
- package/lib/api-handlers.d.ts +0 -25
- package/lib/config.d.ts +0 -14
- package/lib/file-manager.d.ts +0 -124
- package/lib/index.d.ts +0 -17
- package/lib/logger.d.ts +0 -9
- package/lib/message-handler.d.ts +0 -35
- package/lib/types.d.ts +0 -57
- package/lib/utils.d.ts +0 -18
- package/src/api-handlers.ts +0 -646
- package/src/file-manager.ts +0 -1436
- package/src/message-handler.ts +0 -507
- package/src/utils.ts +0 -189
|
@@ -0,0 +1,2674 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @FileDescription: 消息处理模块
|
|
3
|
+
* @Author: Stapxs
|
|
4
|
+
* @Date:
|
|
5
|
+
* 2022/11/1
|
|
6
|
+
* 2022/12/7
|
|
7
|
+
* 2024/9/2
|
|
8
|
+
* @Version:
|
|
9
|
+
* 1.0 - 初始版本
|
|
10
|
+
* 2.0 - 重构为 ts 版本,修改 Vue3 相关变更
|
|
11
|
+
* 3.0 - 优化更优雅的代码结构
|
|
12
|
+
* @Description: 此模块用于拆分和保存/处理 bot 返回的各类信息,整个运行时数据也保存在这儿。
|
|
13
|
+
*/
|
|
14
|
+
import qed from '../assets/qed.txt?raw'
|
|
15
|
+
|
|
16
|
+
import app from '../main'
|
|
17
|
+
import Option from './option'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
buildMsgList,
|
|
22
|
+
getMsgData,
|
|
23
|
+
parseMsgList,
|
|
24
|
+
getMsgRawTxt,
|
|
25
|
+
hasAtMe,
|
|
26
|
+
parseCQ,
|
|
27
|
+
updateBaseOnMsgList,
|
|
28
|
+
updateLastestHistory,
|
|
29
|
+
sendMsgAppendInfo,
|
|
30
|
+
} from '../function/utils/msgUtil'
|
|
31
|
+
import { parseSatoriMarkup } from '../function/satori-model'
|
|
32
|
+
import {
|
|
33
|
+
delay,
|
|
34
|
+
getViewTime,
|
|
35
|
+
randomNum,
|
|
36
|
+
} from '../function/utils/systemUtil'
|
|
37
|
+
import {
|
|
38
|
+
reloadUsers,
|
|
39
|
+
reloadCookies,
|
|
40
|
+
updateMenu,
|
|
41
|
+
loadJsonMap,
|
|
42
|
+
sendIdentifyData,
|
|
43
|
+
sendStatEvent,
|
|
44
|
+
} from '../function/utils/appUtil'
|
|
45
|
+
|
|
46
|
+
const SATORI_MARKUP_PATTERN = /<(?:img|image|audio|video|file|mface|face|quote|at|forward|json|xml|markdown|keyboard|p|br|i18n|a|text|sharp)(?:\s|\/|>)/i
|
|
47
|
+
|
|
48
|
+
function splitSatoriMarkupText(message: any[]): any[] {
|
|
49
|
+
return message.flatMap((segment) => {
|
|
50
|
+
if (
|
|
51
|
+
segment?.type === 'text' &&
|
|
52
|
+
typeof segment.text === 'string' &&
|
|
53
|
+
SATORI_MARKUP_PATTERN.test(segment.text)
|
|
54
|
+
) {
|
|
55
|
+
return parseSatoriMarkup(segment.text)
|
|
56
|
+
}
|
|
57
|
+
return [segment]
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
import { reactive, markRaw, nextTick } from 'vue'
|
|
61
|
+
import { PopInfo, PopType, Logger, LogType } from './base'
|
|
62
|
+
import { isDebugMode } from './debug'
|
|
63
|
+
import { Connector, fetchForwardMessage, getCachedUserAvatar, loadGroupMembersFromCache, login, requestUserAvatar, saveConnectionToHistory, saveSentSelfMessage } from './connect'
|
|
64
|
+
import {
|
|
65
|
+
GroupFileElem,
|
|
66
|
+
GroupFileFolderElem,
|
|
67
|
+
GroupMemberInfoElem,
|
|
68
|
+
UserFriendElem,
|
|
69
|
+
UserGroupElem,
|
|
70
|
+
MsgItemElem,
|
|
71
|
+
type Session,
|
|
72
|
+
} from './elements/information'
|
|
73
|
+
import { NotifyInfo } from './elements/system'
|
|
74
|
+
import { Notify } from './notify'
|
|
75
|
+
import { backend } from '../runtime/backend'
|
|
76
|
+
import { dbRevokeMessage, saveMessagesWithSideEffects } from './utils/localHistoryUtil'
|
|
77
|
+
import { addDownloadTask, completeUploadTask } from '../components/FileManager.vue'
|
|
78
|
+
import { refreshFavicon } from './favicon'
|
|
79
|
+
import { Img } from './model/img'
|
|
80
|
+
import { ensurePinyinLoaded, getPinyin, isPinyinReady } from './utils/pinyin'
|
|
81
|
+
import { useAuthStore } from '../state/auth'
|
|
82
|
+
import { useContactStore } from '../state/contact'
|
|
83
|
+
import { useChatStore } from '../state/chat'
|
|
84
|
+
import { useConnectionStore } from '../state/connection'
|
|
85
|
+
import { useStickerStore } from '../state/sticker'
|
|
86
|
+
import { useUIStore } from '../state/ui'
|
|
87
|
+
import { useSettingsStore } from '../state/settings'
|
|
88
|
+
import { useQzoneStore } from '../state/qzone'
|
|
89
|
+
import {
|
|
90
|
+
getSessionId,
|
|
91
|
+
getMissingGroupPreviewSessions,
|
|
92
|
+
mergeEarlySessionContacts,
|
|
93
|
+
normalizeSessionId,
|
|
94
|
+
resolveIncomingSession,
|
|
95
|
+
} from './utils/sessionUtil'
|
|
96
|
+
|
|
97
|
+
const popInfo = new PopInfo()
|
|
98
|
+
// eslint-disable-next-line
|
|
99
|
+
const msgPaths = import.meta.glob("../assets/pathMap/*.yaml", { eager: true })
|
|
100
|
+
// 取出包含 Lagrange.OneBot.yaml 的那条
|
|
101
|
+
const msgPathAt = Object.keys(msgPaths).find((item) => {
|
|
102
|
+
return item.indexOf('Lagrange.OneBot.yaml') > 0
|
|
103
|
+
})
|
|
104
|
+
let msgPath = {} as { [key: string]: any }
|
|
105
|
+
if (msgPathAt != undefined) {
|
|
106
|
+
msgPath = (msgPaths[msgPathAt] as any).default
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getDataObject(value: unknown): Record<string, unknown> {
|
|
110
|
+
return typeof value === 'object' && value !== null
|
|
111
|
+
? value as Record<string, unknown>
|
|
112
|
+
: {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getString(value: unknown): string {
|
|
116
|
+
if (value === null || value === undefined) return ''
|
|
117
|
+
return typeof value === 'string' ? value : String(value)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 其他 tag
|
|
121
|
+
let listLoadTimes = 0
|
|
122
|
+
const logger = new Logger()
|
|
123
|
+
let firstHeartbeatTime = -1
|
|
124
|
+
let heartbeatTime = -1
|
|
125
|
+
const MILLISECONDS_PER_SECOND = 1000
|
|
126
|
+
const META_EVENT_WATCHDOG = {
|
|
127
|
+
// Give one missed heartbeat plus a small network/main-thread grace window before forcing a disconnect.
|
|
128
|
+
timeoutMultiplier: 2,
|
|
129
|
+
graceSeconds: 5,
|
|
130
|
+
}
|
|
131
|
+
let loginWaveTimer: any = null
|
|
132
|
+
|
|
133
|
+
export function setLoginWaveTimer(timer: any) {
|
|
134
|
+
loginWaveTimer = timer
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function clearLoginWaveTimer() {
|
|
138
|
+
if (loginWaveTimer !== null) {
|
|
139
|
+
clearInterval(loginWaveTimer)
|
|
140
|
+
loginWaveTimer = null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const groupPreviewHydrator = (() => {
|
|
145
|
+
const intervalMs = 150
|
|
146
|
+
let queue: Session[] = []
|
|
147
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
148
|
+
|
|
149
|
+
function stop() {
|
|
150
|
+
if (timer) clearTimeout(timer)
|
|
151
|
+
timer = undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function tick() {
|
|
155
|
+
timer = undefined
|
|
156
|
+
const contactStore = useContactStore()
|
|
157
|
+
const session = queue.shift()
|
|
158
|
+
if (session) {
|
|
159
|
+
const sessionId = getSessionId(session)
|
|
160
|
+
if (
|
|
161
|
+
sessionId !== 0 &&
|
|
162
|
+
sessionId !== '' &&
|
|
163
|
+
!session.time &&
|
|
164
|
+
!session.raw_msg &&
|
|
165
|
+
!contactStore.baseOnMsgList.has(normalizeSessionId(sessionId))
|
|
166
|
+
) {
|
|
167
|
+
// userList 与 baseOnMsgList 共享同一个会话对象;历史响应会原地补全预览。
|
|
168
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(sessionId), session)
|
|
169
|
+
updateLastestHistory(session)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (queue.length > 0) {
|
|
174
|
+
timer = setTimeout(tick, intervalMs)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function start() {
|
|
179
|
+
if (!timer && queue.length > 0) tick()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
scheduleMissingSessions() {
|
|
184
|
+
const contactStore = useContactStore()
|
|
185
|
+
const settingsStore = useSettingsStore()
|
|
186
|
+
if (settingsStore.sysConfig.session_display_mode !== 'all') return
|
|
187
|
+
|
|
188
|
+
const queuedIds = new Set(queue.map((item) => normalizeSessionId(getSessionId(item))))
|
|
189
|
+
getMissingGroupPreviewSessions(
|
|
190
|
+
contactStore.userList,
|
|
191
|
+
contactStore.baseOnMsgList,
|
|
192
|
+
).forEach((item) => {
|
|
193
|
+
const sessionId = normalizeSessionId(getSessionId(item))
|
|
194
|
+
if (!queuedIds.has(sessionId)) {
|
|
195
|
+
queue.push(item)
|
|
196
|
+
queuedIds.add(sessionId)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
start()
|
|
200
|
+
},
|
|
201
|
+
reset() {
|
|
202
|
+
stop()
|
|
203
|
+
queue = []
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
})()
|
|
207
|
+
|
|
208
|
+
function resolveContactPinyinName(item: UserFriendElem | UserGroupElem) {
|
|
209
|
+
if ((item as UserFriendElem).group_id) {
|
|
210
|
+
return (item as UserFriendElem).group_name ?? ''
|
|
211
|
+
}
|
|
212
|
+
return `${(item as UserGroupElem).nickname ?? ''},${(item as UserGroupElem).remark ?? ''}`
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function resolvePinyinFirstChar(value: string) {
|
|
216
|
+
return getPinyin(value)
|
|
217
|
+
.main
|
|
218
|
+
.at(0)
|
|
219
|
+
?.substring(0, 1)
|
|
220
|
+
.toUpperCase() ?? ' '
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function sortContactListByPinyin<T extends UserFriendElem | UserGroupElem>(list: T[]) {
|
|
224
|
+
list.sort((a, b) => {
|
|
225
|
+
if (a.py_start && b.py_start) {
|
|
226
|
+
return a.py_start.charCodeAt(0) - b.py_start.charCodeAt(0)
|
|
227
|
+
}
|
|
228
|
+
return 0
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildPinyinForContacts(
|
|
233
|
+
list: (UserFriendElem | UserGroupElem)[],
|
|
234
|
+
startIndex = 0,
|
|
235
|
+
onDone?: () => void,
|
|
236
|
+
) {
|
|
237
|
+
if (!isPinyinReady()) {
|
|
238
|
+
onDone?.()
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const batchSize = 100
|
|
243
|
+
const endIndex = Math.min(startIndex + batchSize, list.length)
|
|
244
|
+
|
|
245
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
246
|
+
const item = list[index]
|
|
247
|
+
item.py_name = getPinyin(resolveContactPinyinName(item))
|
|
248
|
+
item.py_start = item.py_name.main.at(0)?.substring(0, 1).toUpperCase() ?? ' '
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (endIndex >= list.length) {
|
|
252
|
+
onDone?.()
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
buildPinyinForContacts(list, endIndex, onDone)
|
|
258
|
+
}, 0)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function hydrateContactPinyinLater(list: (UserFriendElem | UserGroupElem)[]) {
|
|
262
|
+
const contactStore = useContactStore()
|
|
263
|
+
|
|
264
|
+
const applyHydration = () => {
|
|
265
|
+
buildPinyinForContacts(list, 0, () => {
|
|
266
|
+
sortContactListByPinyin(list)
|
|
267
|
+
contactStore.userList = [...contactStore.userList]
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (isPinyinReady()) {
|
|
272
|
+
applyHydration()
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
void ensurePinyinLoaded().then((loaded) => {
|
|
277
|
+
if (!loaded) return
|
|
278
|
+
applyHydration()
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function clearMetaEventWatchdog() {
|
|
283
|
+
const connectionStore = useConnectionStore()
|
|
284
|
+
if (connectionStore.metaEventWatchTimer) {
|
|
285
|
+
clearTimeout(connectionStore.metaEventWatchTimer)
|
|
286
|
+
connectionStore.metaEventWatchTimer = undefined
|
|
287
|
+
}
|
|
288
|
+
connectionStore.metaEventTimeoutTriggered = false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function refreshMetaEventWatchdog(intervalSeconds: number) {
|
|
292
|
+
if (intervalSeconds <= 0) return
|
|
293
|
+
|
|
294
|
+
const connectionStore = useConnectionStore()
|
|
295
|
+
if (connectionStore.metaEventWatchTimer) {
|
|
296
|
+
clearTimeout(connectionStore.metaEventWatchTimer)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const timeoutSeconds = Math.ceil(
|
|
300
|
+
Math.max(
|
|
301
|
+
intervalSeconds * META_EVENT_WATCHDOG.timeoutMultiplier,
|
|
302
|
+
intervalSeconds + META_EVENT_WATCHDOG.graceSeconds,
|
|
303
|
+
),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
connectionStore.metaEventTimeoutTriggered = false
|
|
307
|
+
connectionStore.metaEventWatchTimer = setTimeout(() => {
|
|
308
|
+
if (connectionStore.metaEventTimeoutTriggered) return
|
|
309
|
+
connectionStore.metaEventTimeoutTriggered = true
|
|
310
|
+
connectionStore.metaEventWatchTimer = undefined
|
|
311
|
+
logger.add(LogType.WS, '心跳包超时,准备断开连接')
|
|
312
|
+
Connector.forceDisconnect('心跳包超时')
|
|
313
|
+
}, timeoutSeconds * MILLISECONDS_PER_SECOND)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getObservedHeartbeatIntervalSeconds(msg: { [key: string]: any }) {
|
|
317
|
+
const currentHeartbeatTimeSeconds = Number(msg.time)
|
|
318
|
+
if (
|
|
319
|
+
firstHeartbeatTime > 0 &&
|
|
320
|
+
Number.isFinite(currentHeartbeatTimeSeconds) &&
|
|
321
|
+
currentHeartbeatTimeSeconds > firstHeartbeatTime
|
|
322
|
+
) {
|
|
323
|
+
return currentHeartbeatTimeSeconds - firstHeartbeatTime
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return -1
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getHeartbeatIntervalSeconds(msg: { [key: string]: any }) {
|
|
330
|
+
// OneBot heartbeat `interval` is reported in milliseconds; `time` is a Unix timestamp in seconds.
|
|
331
|
+
const reportedIntervalMilliseconds = Number(msg.interval)
|
|
332
|
+
if (Number.isFinite(reportedIntervalMilliseconds) && reportedIntervalMilliseconds > 0) {
|
|
333
|
+
return reportedIntervalMilliseconds / MILLISECONDS_PER_SECOND
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return getObservedHeartbeatIntervalSeconds(msg)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function dispatch(raw: string | { [k: string]: any }, echo?: string) {
|
|
340
|
+
let msg: any;
|
|
341
|
+
|
|
342
|
+
// 1) 如有需要先 parse
|
|
343
|
+
if (typeof raw === 'string') {
|
|
344
|
+
try {
|
|
345
|
+
msg = JSON.parse(raw);
|
|
346
|
+
} catch {
|
|
347
|
+
if (!raw.includes('"meta_event_type":"heartbeat"')) {
|
|
348
|
+
logger.add(LogType.WS, 'GET:' + raw);
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
msg = raw;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 2) 決定 name/key
|
|
357
|
+
const name = echo ? echo.split('_')[0] : msg.post_type === 'notice' ? msg.sub_type ?? msg.notice_type : msg.post_type;
|
|
358
|
+
|
|
359
|
+
// 3) 安全調用 handler
|
|
360
|
+
try {
|
|
361
|
+
const fn = handlers[name];
|
|
362
|
+
if (!fn) throw new Error(`No handler for "${name}"`);
|
|
363
|
+
const metaArgs = echo ? echo.split('_') : undefined;
|
|
364
|
+
fn(msg, metaArgs);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
logger.error(e as Error, `跳转事件处理错误 - ${name}:\n${JSON.stringify(msg)}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ==============================================================
|
|
371
|
+
const noticeFunctions = {
|
|
372
|
+
/**
|
|
373
|
+
* 心跳包
|
|
374
|
+
*/
|
|
375
|
+
meta_event: (_: string, msg: { [key: string]: any }) => {
|
|
376
|
+
const connectionStore = useConnectionStore()
|
|
377
|
+
if (firstHeartbeatTime == -1) {
|
|
378
|
+
firstHeartbeatTime = 0
|
|
379
|
+
connectionStore.heartbeatTime = 0
|
|
380
|
+
clearMetaEventWatchdog()
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
if (firstHeartbeatTime == 0) {
|
|
384
|
+
firstHeartbeatTime = msg.time
|
|
385
|
+
connectionStore.lastHeartbeatTime = msg.time
|
|
386
|
+
clearMetaEventWatchdog()
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
if (firstHeartbeatTime != -1 && heartbeatTime == -1) {
|
|
390
|
+
// 计算心跳时间
|
|
391
|
+
heartbeatTime = getHeartbeatIntervalSeconds(msg)
|
|
392
|
+
}
|
|
393
|
+
// 记录心跳状态
|
|
394
|
+
if (heartbeatTime != -1) {
|
|
395
|
+
connectionStore.heartbeatTime = heartbeatTime
|
|
396
|
+
connectionStore.oldHeartbeatTime = connectionStore.lastHeartbeatTime
|
|
397
|
+
connectionStore.lastHeartbeatTime = msg.time
|
|
398
|
+
refreshMetaEventWatchdog(heartbeatTime)
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 新消息
|
|
404
|
+
*/
|
|
405
|
+
message_sent: newMsg,
|
|
406
|
+
message: newMsg,
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 请求
|
|
410
|
+
*/
|
|
411
|
+
request: (_: string, msg: { [key: string]: any }) => {
|
|
412
|
+
const contactStore = useContactStore()
|
|
413
|
+
if (contactStore.systemNoticesList) {
|
|
414
|
+
contactStore.systemNoticesList.push(msg)
|
|
415
|
+
} else {
|
|
416
|
+
contactStore.systemNoticesList = [msg]
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 好友变动
|
|
422
|
+
*/
|
|
423
|
+
friend: (_: string, msg: { [key: string]: any }) => {
|
|
424
|
+
// 重新加载联系人列表
|
|
425
|
+
reloadUsers()
|
|
426
|
+
switch (msg.sub_type) {
|
|
427
|
+
case 'increase': {
|
|
428
|
+
// 添加系统通知
|
|
429
|
+
new PopInfo().add(
|
|
430
|
+
PopType.INFO,
|
|
431
|
+
app.config.globalProperties.$t('添加好友 {name} 成功!', {
|
|
432
|
+
name: msg.nickname,
|
|
433
|
+
}),
|
|
434
|
+
)
|
|
435
|
+
break
|
|
436
|
+
}
|
|
437
|
+
case 'decrease': {
|
|
438
|
+
if (isDebugMode()) {
|
|
439
|
+
// eslint-disable-next-line no-console
|
|
440
|
+
console.log(
|
|
441
|
+
'%c消失了一个好友:' +
|
|
442
|
+
msg.nickname +
|
|
443
|
+
'(' +
|
|
444
|
+
msg.user_id +
|
|
445
|
+
')',
|
|
446
|
+
'color:red;',
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
break
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* 消息撤回
|
|
456
|
+
*/
|
|
457
|
+
group_recall: revokeMsg,
|
|
458
|
+
friend_recall: revokeMsg,
|
|
459
|
+
recall: revokeMsg,
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 表情回应
|
|
463
|
+
*/
|
|
464
|
+
group_msg_emoji_like: (_: string, msg: { [key: string]: any }) => {
|
|
465
|
+
const chatStore = useChatStore()
|
|
466
|
+
const msgId = msg.message_id
|
|
467
|
+
const emojiList = msg.likes
|
|
468
|
+
// 寻找消息
|
|
469
|
+
chatStore.messageList.forEach((item, index) => {
|
|
470
|
+
if (item.message_id === msgId) {
|
|
471
|
+
chatStore.messageList[index].emoji_like = emojiList
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 群禁言
|
|
478
|
+
*/
|
|
479
|
+
group_ban: (_: string, msg: { [key: string]: any }) => {
|
|
480
|
+
const authStore = useAuthStore()
|
|
481
|
+
const chatStore = useChatStore()
|
|
482
|
+
const groupId = msg.group_id
|
|
483
|
+
const userId = msg.user_id
|
|
484
|
+
const status = msg.sub_type === 'ban' ? true : false
|
|
485
|
+
const duration = msg.duration ?? 0 // 秒
|
|
486
|
+
|
|
487
|
+
// 如果是自己,更新禁言时间
|
|
488
|
+
if (
|
|
489
|
+
userId == authStore.loginInfo.uin &&
|
|
490
|
+
groupId == chatStore.chatInfo.show.id
|
|
491
|
+
) {
|
|
492
|
+
if (status)
|
|
493
|
+
chatStore.chatInfo.info.me_info.shut_up_timestamp =
|
|
494
|
+
(new Date().getTime() + duration * 1000) / 1000
|
|
495
|
+
else chatStore.chatInfo.info.me_info.shut_up_timestamp = 0
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// 只有在当前群才会显示
|
|
499
|
+
if (groupId == chatStore.chatInfo.show.id)
|
|
500
|
+
chatStore.messageList.push(msg)
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 踢人
|
|
505
|
+
*/
|
|
506
|
+
kick: (_: string, msg: { [key: string]: any }) => {
|
|
507
|
+
const chatStore = useChatStore()
|
|
508
|
+
const groupId = msg.group_id
|
|
509
|
+
if (groupId == chatStore.chatInfo.show.id) {
|
|
510
|
+
// 稍微等一下再刷新成员列表
|
|
511
|
+
delay(1000).then(() => {
|
|
512
|
+
void loadGroupMembersFromCache(String(chatStore.chatInfo.show.id))
|
|
513
|
+
return delay(1000)
|
|
514
|
+
}).then(() => {
|
|
515
|
+
void loadGroupMembersFromCache(String(chatStore.chatInfo.show.id))
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 戳一戳
|
|
522
|
+
*/
|
|
523
|
+
poke: (_: string, msg: { [key: string]: any }) => {
|
|
524
|
+
const { $t } = app.config.globalProperties
|
|
525
|
+
const authStore = useAuthStore()
|
|
526
|
+
const chatStore = useChatStore()
|
|
527
|
+
|
|
528
|
+
const groupId = msg.group_id
|
|
529
|
+
const userIds = [msg.user_id, msg.target_id]
|
|
530
|
+
const info = msg.raw_info
|
|
531
|
+
|
|
532
|
+
// 如果的当前打开的会话
|
|
533
|
+
if (groupId == chatStore.chatInfo.show.id) {
|
|
534
|
+
let str = ''
|
|
535
|
+
const userInfo = [] as { txt: string; isMe: boolean }[]
|
|
536
|
+
// 用户列表
|
|
537
|
+
userIds.forEach((id) => {
|
|
538
|
+
if (id == authStore.loginInfo.uin) {
|
|
539
|
+
userInfo.push({
|
|
540
|
+
txt: $t('你'),
|
|
541
|
+
isMe: true,
|
|
542
|
+
})
|
|
543
|
+
} else {
|
|
544
|
+
// 到群成员列表中去找这个人
|
|
545
|
+
const user = chatStore.chatInfo.info.group_members.find(
|
|
546
|
+
(item) => {
|
|
547
|
+
return item.user_id == id
|
|
548
|
+
},
|
|
549
|
+
)
|
|
550
|
+
if (user)
|
|
551
|
+
userInfo.push({
|
|
552
|
+
txt: `<span>${user.nickname}</span>`,
|
|
553
|
+
isMe: false,
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
})
|
|
557
|
+
// 遍历内容段
|
|
558
|
+
let getQQTimes = 0
|
|
559
|
+
info.forEach((item: any) => {
|
|
560
|
+
switch (item.type) {
|
|
561
|
+
case 'img':
|
|
562
|
+
str += `<img src="${backend.proxyUrl(item.src)}"/>`
|
|
563
|
+
break
|
|
564
|
+
case 'nor':
|
|
565
|
+
str += item.txt
|
|
566
|
+
break
|
|
567
|
+
case 'qq': {
|
|
568
|
+
str += userInfo[getQQTimes].txt
|
|
569
|
+
getQQTimes++
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
// 插入系统消息
|
|
574
|
+
msg.str = str
|
|
575
|
+
msg.pokeMe = userInfo[1].isMe
|
|
576
|
+
chatStore.messageList.push(msg)
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
approve: (_: string, msg: { [key: string]: any }) => {
|
|
581
|
+
const { $t } = app.config.globalProperties
|
|
582
|
+
const chatStore = useChatStore()
|
|
583
|
+
|
|
584
|
+
const groupId = msg.group_id
|
|
585
|
+
const userId = msg.user_id
|
|
586
|
+
|
|
587
|
+
// 如果的当前打开的会话
|
|
588
|
+
if (groupId == chatStore.chatInfo.show.id) {
|
|
589
|
+
// 刷新群成员列表
|
|
590
|
+
void loadGroupMembersFromCache(String(groupId))
|
|
591
|
+
// 获取到用户信息
|
|
592
|
+
const user = chatStore.chatInfo.info.group_members.find(
|
|
593
|
+
(item) => {
|
|
594
|
+
return item.user_id == userId
|
|
595
|
+
},
|
|
596
|
+
)
|
|
597
|
+
// 插入入群通知
|
|
598
|
+
if (user) {
|
|
599
|
+
const str = $t('{name} 加入了群聊', {
|
|
600
|
+
name: user.nickname,
|
|
601
|
+
})
|
|
602
|
+
msg.str = str
|
|
603
|
+
chatStore.messageList.push(msg)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
input_status: (_: string, msg: { [key: string]: any }) => {
|
|
609
|
+
const { $t } = app.config.globalProperties
|
|
610
|
+
const chatStore = useChatStore()
|
|
611
|
+
const sender = msg.user_id
|
|
612
|
+
if (chatStore.chatInfo.show.id == sender) {
|
|
613
|
+
// 使用客户端返回的具体状态文本
|
|
614
|
+
if (msg.status_text) {
|
|
615
|
+
chatStore.chatInfo.show.appendInfo = $t(msg.status_text)
|
|
616
|
+
setTimeout(() => {
|
|
617
|
+
chatStore.chatInfo.show.appendInfo = undefined
|
|
618
|
+
}, 10000)
|
|
619
|
+
} else {
|
|
620
|
+
// 对方停止输入时,会有一个空的 input_status 消息
|
|
621
|
+
chatStore.chatInfo.show.appendInfo = undefined
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
} as { [key: string]: (name: string, msg: { [key: string]: any }) => void }
|
|
626
|
+
|
|
627
|
+
const msgFunctions = {
|
|
628
|
+
/**
|
|
629
|
+
* 修改群成员信息回调
|
|
630
|
+
*/
|
|
631
|
+
updateGroupMemberInfo: () => {
|
|
632
|
+
const { $t } = app.config.globalProperties
|
|
633
|
+
const uiStore = useUIStore()
|
|
634
|
+
const chatStore = useChatStore()
|
|
635
|
+
const popInfo = {
|
|
636
|
+
title: $t('操作'),
|
|
637
|
+
html: `<span>${$t('正在确认操作……')}</span>`
|
|
638
|
+
}
|
|
639
|
+
uiStore.popBoxList.push(popInfo)
|
|
640
|
+
// 稍微等一下再刷新成员列表
|
|
641
|
+
delay(1000).then(() => {
|
|
642
|
+
void loadGroupMembersFromCache(String(chatStore.chatInfo.show.id))
|
|
643
|
+
return delay(1000)
|
|
644
|
+
}).then(() => {
|
|
645
|
+
void loadGroupMembersFromCache(String(chatStore.chatInfo.show.id))
|
|
646
|
+
uiStore.popBoxList.shift()
|
|
647
|
+
})
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* 保存 Bot 信息
|
|
652
|
+
*/
|
|
653
|
+
getVersionInfo: (_: string, msg: { [key: string]: any }) => {
|
|
654
|
+
const data = getMsgData('version_info', msg, msgPath.version_info)[0]
|
|
655
|
+
|
|
656
|
+
if (data) {
|
|
657
|
+
// 如果 runtime 存在(即不是第一次连接),且 app_name 不同,重置 runtime
|
|
658
|
+
const authStore = useAuthStore()
|
|
659
|
+
resetRimtime(
|
|
660
|
+
authStore.botInfo.app_name != data.app_name && !login.status,
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
authStore.botInfo = data
|
|
664
|
+
if (Option.get('open_ga_bot') !== false) {
|
|
665
|
+
const appVersion = data.app_version ? ',' + data.app_version : ''
|
|
666
|
+
const appInfo = data.app_name ? data.app_name + appVersion : '(未知)'
|
|
667
|
+
|
|
668
|
+
sendStatEvent('connect', { method: data.app_name })
|
|
669
|
+
sendIdentifyData({ bot_version: appInfo })
|
|
670
|
+
}
|
|
671
|
+
if (!login.status) {
|
|
672
|
+
// 尝试动态载入对应的 pathMap
|
|
673
|
+
if (data.app_name !== undefined) {
|
|
674
|
+
const getMap = loadJsonMap(data.app_name)
|
|
675
|
+
if (getMap != null) msgPath = getMap
|
|
676
|
+
}
|
|
677
|
+
// 继续获取后续内容
|
|
678
|
+
Connector.send('get_login_info', {}, 'getLoginInfo')
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* 保存账号信息
|
|
685
|
+
*/
|
|
686
|
+
getLoginInfo: (_: string, msg: { [key: string]: any }) => {
|
|
687
|
+
const msgBody = getMsgData('login_info', msg, msgPath.login_info)
|
|
688
|
+
if (msgBody) {
|
|
689
|
+
const data = msgBody[0]
|
|
690
|
+
const authStore = useAuthStore()
|
|
691
|
+
|
|
692
|
+
// 如果 runtime 存在(即不是第一次连接),且 uin 不同,重置 runtime
|
|
693
|
+
resetRimtime(authStore.loginInfo.uin != data.uin && !login.status)
|
|
694
|
+
|
|
695
|
+
// 完成登陆初始化
|
|
696
|
+
authStore.loginInfo = data
|
|
697
|
+
login.status = true
|
|
698
|
+
|
|
699
|
+
// 保存用户信息到连接历史
|
|
700
|
+
saveConnectionToHistory(login.address, login.token, data.uin, data.nickname)
|
|
701
|
+
|
|
702
|
+
// 显示账户菜单
|
|
703
|
+
updateMenu({
|
|
704
|
+
parent: 'account',
|
|
705
|
+
id: 'userName',
|
|
706
|
+
action: 'label',
|
|
707
|
+
value: data.nickname,
|
|
708
|
+
})
|
|
709
|
+
const title = `${data.nickname} `
|
|
710
|
+
if (backend.platform == 'web') {
|
|
711
|
+
document.title = title + '- Stapxs QQ Lite'
|
|
712
|
+
} else {
|
|
713
|
+
document.title = title
|
|
714
|
+
backend.call(undefined, 'win:setTitle', false, title)
|
|
715
|
+
}
|
|
716
|
+
// 结束登录页面的水波动画
|
|
717
|
+
clearLoginWaveTimer()
|
|
718
|
+
// 跳转标签卡
|
|
719
|
+
const barMsg = document.getElementById('bar-msg')
|
|
720
|
+
if (barMsg != null) barMsg.click()
|
|
721
|
+
// 加载列表消息
|
|
722
|
+
reloadUsers()
|
|
723
|
+
reloadCookies()
|
|
724
|
+
// 尝试加载 QZore 列表
|
|
725
|
+
if (authStore.jsonMap.get_qzone_feed) {
|
|
726
|
+
Connector.send(authStore.jsonMap.get_qzone_feed.name, {}, 'getQzoneFeed')
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* 补充登录信息
|
|
733
|
+
* @deprecated 此功能在 OICQ 后的 bot 中没有再实现,暂时保留
|
|
734
|
+
*/
|
|
735
|
+
getMoreLoginInfo: (_: string, msg: { [key: string]: any }) => {
|
|
736
|
+
const authStore = useAuthStore()
|
|
737
|
+
authStore.loginInfo.info = msg.data.data.result.buddy.info_list[0]
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* 保存好友列表
|
|
742
|
+
*/
|
|
743
|
+
getGroupList: (_: string, msg: { [key: string]: any }) => {
|
|
744
|
+
saveUser(msg, 'group')
|
|
745
|
+
},
|
|
746
|
+
getFriendList: (_: string, msg: { [key: string]: any }) => {
|
|
747
|
+
saveUser(msg, 'friend')
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* 保存分组信息(独立保存)
|
|
752
|
+
*/
|
|
753
|
+
getFriendCategory: (_: string, msg: { [key: string]: any }) => {
|
|
754
|
+
const contactStore = useContactStore()
|
|
755
|
+
const list = getMsgData(
|
|
756
|
+
'friend_category',
|
|
757
|
+
msg,
|
|
758
|
+
msgPath.friend_category,
|
|
759
|
+
) as {
|
|
760
|
+
class_id: number
|
|
761
|
+
class_name: string
|
|
762
|
+
sort_id: number
|
|
763
|
+
users: number[]
|
|
764
|
+
}[]
|
|
765
|
+
if (list != undefined) {
|
|
766
|
+
saveClassInfo(list)
|
|
767
|
+
}
|
|
768
|
+
// 刷新用户列表的分类信息
|
|
769
|
+
list.forEach((item) => {
|
|
770
|
+
item.users.forEach((id) => {
|
|
771
|
+
contactStore.userList.forEach((user) => {
|
|
772
|
+
if (user.user_id == id && user.class_id == undefined) {
|
|
773
|
+
user.class_id = item.class_id
|
|
774
|
+
user.class_name = item.class_name
|
|
775
|
+
}
|
|
776
|
+
})
|
|
777
|
+
})
|
|
778
|
+
})
|
|
779
|
+
},
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* 获取群成员信息
|
|
783
|
+
*/
|
|
784
|
+
getUserInfoInGroup: (_: string, msg: { [key: string]: any }) => {
|
|
785
|
+
const chatStore = useChatStore()
|
|
786
|
+
const payload = getDataObject(getDataObject(msg).data ?? msg)
|
|
787
|
+
const info: Record<string, unknown> = { ...payload }
|
|
788
|
+
const user = getDataObject(payload.user)
|
|
789
|
+
if (info.user_id == null && user.id != null) {
|
|
790
|
+
info.user_id = user.id
|
|
791
|
+
}
|
|
792
|
+
if (info.nickname == null && (user.name != null || user.nick != null)) {
|
|
793
|
+
info.nickname = user.name ?? user.nick
|
|
794
|
+
}
|
|
795
|
+
if (info.card == null && (payload.nick != null || payload.name != null)) {
|
|
796
|
+
info.card = payload.nick ?? payload.name
|
|
797
|
+
}
|
|
798
|
+
const shutUp = Number(info.shut_up_timestamp ?? info.shutup_time ?? 0)
|
|
799
|
+
if (shutUp * 1000 < Date.now()) {
|
|
800
|
+
info.shut_up_timestamp = 0
|
|
801
|
+
}
|
|
802
|
+
chatStore.chatInfo.info.me_info = info
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* 保存群成员列表
|
|
807
|
+
*/
|
|
808
|
+
getGroupMemberList: (_: string, msg: { [key: string]: any }) => {
|
|
809
|
+
const chatStore = useChatStore()
|
|
810
|
+
const raw = getDataObject(msg).data
|
|
811
|
+
if (!Array.isArray(raw)) return
|
|
812
|
+
const data = raw as GroupMemberInfoElem[]
|
|
813
|
+
const sortAndSaveMembers = () => {
|
|
814
|
+
const adminList = data.filter((item: GroupMemberInfoElem) => {
|
|
815
|
+
return item.role === 'admin'
|
|
816
|
+
})
|
|
817
|
+
adminList.sort((a, b) => {
|
|
818
|
+
if (a.py_start && b.py_start) {
|
|
819
|
+
return a.py_start.charCodeAt(0) - b.py_start.charCodeAt(0)
|
|
820
|
+
}
|
|
821
|
+
return 0
|
|
822
|
+
})
|
|
823
|
+
const createrList = data.filter((item: GroupMemberInfoElem) => {
|
|
824
|
+
return item.role === 'owner'
|
|
825
|
+
})
|
|
826
|
+
const memberList = data.filter((item: GroupMemberInfoElem) => {
|
|
827
|
+
return item.role !== 'admin' && item.role !== 'owner'
|
|
828
|
+
})
|
|
829
|
+
memberList.sort((a, b) => {
|
|
830
|
+
if (a.py_start && b.py_start) {
|
|
831
|
+
return a.py_start.charCodeAt(0) - b.py_start.charCodeAt(0)
|
|
832
|
+
}
|
|
833
|
+
return 0
|
|
834
|
+
})
|
|
835
|
+
chatStore.chatInfo.info.group_members = createrList.concat(adminList.concat(memberList))
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
data.forEach((item: any) => {
|
|
839
|
+
let name: string
|
|
840
|
+
if (item.card != undefined && item.card != '') {
|
|
841
|
+
name = item.card
|
|
842
|
+
} else if (item.nickname != undefined && item.nickname != '') {
|
|
843
|
+
name = item.nickname
|
|
844
|
+
} else {
|
|
845
|
+
name = item.user_id.toString()
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// 获取拼音首字母
|
|
849
|
+
item.py_start = resolvePinyinFirstChar(name.substring(0, 1))
|
|
850
|
+
})
|
|
851
|
+
sortAndSaveMembers()
|
|
852
|
+
|
|
853
|
+
if (!isPinyinReady()) {
|
|
854
|
+
void ensurePinyinLoaded().then((loaded) => {
|
|
855
|
+
if (!loaded) return
|
|
856
|
+
data.forEach((item: any) => {
|
|
857
|
+
let name: string
|
|
858
|
+
if (item.card != undefined && item.card != '') {
|
|
859
|
+
name = item.card
|
|
860
|
+
} else if (item.nickname != undefined && item.nickname != '') {
|
|
861
|
+
name = item.nickname
|
|
862
|
+
} else {
|
|
863
|
+
name = item.user_id.toString()
|
|
864
|
+
}
|
|
865
|
+
item.py_start = resolvePinyinFirstChar(name.substring(0, 1))
|
|
866
|
+
})
|
|
867
|
+
sortAndSaveMembers()
|
|
868
|
+
})
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* 保存聊天记录
|
|
874
|
+
*/
|
|
875
|
+
getChatHistoryFist: (_: string, msg: { [key: string]: any }) => {
|
|
876
|
+
const uiStore = useUIStore()
|
|
877
|
+
if (msg.data === null) {
|
|
878
|
+
new PopInfo().add(
|
|
879
|
+
PopType.ERR,
|
|
880
|
+
app.config.globalProperties.$t('获取历史记录失败'),
|
|
881
|
+
)
|
|
882
|
+
uiStore.loadHistoryFail = true
|
|
883
|
+
return
|
|
884
|
+
}
|
|
885
|
+
// 无论是否有本地预填充,都以网络数据替换(保证最新消息不遗漏)
|
|
886
|
+
saveMsg(msg)
|
|
887
|
+
},
|
|
888
|
+
getChatHistoryGapFill: (
|
|
889
|
+
_: string,
|
|
890
|
+
msg: { [key: string]: any },
|
|
891
|
+
metaArgs?: string[],
|
|
892
|
+
) => {
|
|
893
|
+
const authStore = useAuthStore()
|
|
894
|
+
const chatStore = useChatStore()
|
|
895
|
+
// echo 格式:getChatHistoryGapFill_<anchorMsgId>
|
|
896
|
+
// anchorMsgId 是 gap 之后第一条消息的 message_id(插入点)
|
|
897
|
+
const anchorMsgId = metaArgs?.[1]
|
|
898
|
+
if (!anchorMsgId || msg.data === null) return
|
|
899
|
+
const rawList = getMsgData('message_list', msg, msgPath.message_list)
|
|
900
|
+
getMessageList(rawList)
|
|
901
|
+
.then((list) => {
|
|
902
|
+
if (!list || list.length === 0) return
|
|
903
|
+
const inserted = insertHistorySegmentAtAnchor(
|
|
904
|
+
chatStore.messageList,
|
|
905
|
+
anchorMsgId,
|
|
906
|
+
list,
|
|
907
|
+
)
|
|
908
|
+
if (inserted.length === chatStore.messageList.length) return
|
|
909
|
+
replaceMessageListInPlace(inserted)
|
|
910
|
+
// 同步存入本地 DB,以便下次直接从本地加载
|
|
911
|
+
saveMessagesWithSideEffects(authStore.loginInfo.uin, list)
|
|
912
|
+
})
|
|
913
|
+
.catch(() => {})
|
|
914
|
+
},
|
|
915
|
+
getChatHistory: (_: string, msg: { [key: string]: any }) => {
|
|
916
|
+
const uiStore = useUIStore()
|
|
917
|
+
if (msg.data === null) {
|
|
918
|
+
new PopInfo().add(
|
|
919
|
+
PopType.ERR,
|
|
920
|
+
app.config.globalProperties.$t('获取历史记录失败'),
|
|
921
|
+
)
|
|
922
|
+
uiStore.loadHistoryFail = true
|
|
923
|
+
uiStore.historyBeforeTime = undefined
|
|
924
|
+
uiStore.nowGetHistory = false
|
|
925
|
+
return
|
|
926
|
+
}
|
|
927
|
+
const pan = document.getElementById('msgPan')
|
|
928
|
+
if (pan) {
|
|
929
|
+
const oldScrollHeight = pan.scrollHeight
|
|
930
|
+
saveMsg(msg, 'top').then(() => {
|
|
931
|
+
uiStore.nowGetHistory = false
|
|
932
|
+
nextTick(() => {
|
|
933
|
+
setTimeout(() => {
|
|
934
|
+
logger.debug(`滚动前高度:${oldScrollHeight},当前高度:${pan.scrollHeight},滚动位置:${pan.scrollHeight - oldScrollHeight}`)
|
|
935
|
+
pan.style.scrollBehavior = 'unset'
|
|
936
|
+
// 纠正滚动位置
|
|
937
|
+
pan.scrollTop = pan.scrollHeight - oldScrollHeight
|
|
938
|
+
pan.style.scrollBehavior = 'smooth'
|
|
939
|
+
}, 200);
|
|
940
|
+
})
|
|
941
|
+
})
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
|
|
945
|
+
getChatHistoryOnMsg: (
|
|
946
|
+
_: string,
|
|
947
|
+
msg: { [key: string]: any },
|
|
948
|
+
echoList: string[],
|
|
949
|
+
) => {
|
|
950
|
+
const contactStore = useContactStore()
|
|
951
|
+
const id = String(echoList[1] ?? '')
|
|
952
|
+
if (id) {
|
|
953
|
+
try {
|
|
954
|
+
// 对消息进行一次格式化处理
|
|
955
|
+
let list = getMsgData('message_list', msg, msgPath.message_list)
|
|
956
|
+
if (list != undefined) {
|
|
957
|
+
list = parseMsgList(
|
|
958
|
+
list,
|
|
959
|
+
msgPath.message_list.type,
|
|
960
|
+
msgPath.message_value,
|
|
961
|
+
)
|
|
962
|
+
// 更新消息列表
|
|
963
|
+
const onmsg = contactStore.baseOnMsgList.get(normalizeSessionId(id))
|
|
964
|
+
if (onmsg && list[0]) {
|
|
965
|
+
Object.assign(onmsg, formatMessageData(list[0], Boolean(onmsg.group_id)))
|
|
966
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(id), onmsg)
|
|
967
|
+
updateBaseOnMsgList()
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (e) {
|
|
971
|
+
// do nothing
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* 发送消息后处理
|
|
978
|
+
*/
|
|
979
|
+
sendMsgBack: (
|
|
980
|
+
_: string,
|
|
981
|
+
msg: { [key: string]: any },
|
|
982
|
+
echoList: string[],
|
|
983
|
+
) => {
|
|
984
|
+
const authStore = useAuthStore()
|
|
985
|
+
const chatStore = useChatStore()
|
|
986
|
+
const rawData = msg.data
|
|
987
|
+
const firstData = Array.isArray(rawData) ? rawData[0] : rawData
|
|
988
|
+
const realMessageId = getString(msg.message_id)
|
|
989
|
+
|| getString(getDataObject(rawData).message_id)
|
|
990
|
+
|| getString(getDataObject(firstData).id)
|
|
991
|
+
|| getString(getDataObject(firstData).message_id)
|
|
992
|
+
if (realMessageId) msg.message_id = realMessageId
|
|
993
|
+
if (echoList[1] == 'forward') {
|
|
994
|
+
// PS:这儿写是写了转发成功,事实上不确定消息有没有真的发送出去(x
|
|
995
|
+
popInfo.add(
|
|
996
|
+
PopType.INFO,
|
|
997
|
+
app.config.globalProperties.$t('消息已转发'),
|
|
998
|
+
)
|
|
999
|
+
} else if (echoList[1] == 'uuid') {
|
|
1000
|
+
const messageId = echoList[2]
|
|
1001
|
+
// 去 messagelist 里找到这条消息
|
|
1002
|
+
chatStore.messageList.forEach((item) => {
|
|
1003
|
+
if (item.message_id == messageId) {
|
|
1004
|
+
item.message_id = msg.message_id
|
|
1005
|
+
item.fake_msg = false
|
|
1006
|
+
item.revoke = false
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
1009
|
+
})
|
|
1010
|
+
const sentItem = chatStore.messageList.find((item) => {
|
|
1011
|
+
return String(item.fake_message_id ?? item.message_id) === String(messageId)
|
|
1012
|
+
})
|
|
1013
|
+
const chatInfo = chatStore.chatInfo.show
|
|
1014
|
+
const channelType = chatInfo.type === 'group' ? 'group' as const : 'user' as const
|
|
1015
|
+
const channelId = getString(msg.channel_id)
|
|
1016
|
+
|| getString(msg.channelId)
|
|
1017
|
+
|| getString(chatInfo.channel_id)
|
|
1018
|
+
|| String(chatInfo.id)
|
|
1019
|
+
const guildId = getString(msg.guild_id) || getString(msg.guildId) || getString(chatInfo.guild_id)
|
|
1020
|
+
if (realMessageId) {
|
|
1021
|
+
void saveSentSelfMessage({
|
|
1022
|
+
id: messageId,
|
|
1023
|
+
platform: getString(authStore.loginInfo.platform),
|
|
1024
|
+
selfId: getString(authStore.loginInfo.uin),
|
|
1025
|
+
channelId,
|
|
1026
|
+
guildId: guildId || undefined,
|
|
1027
|
+
channelType,
|
|
1028
|
+
messageId: realMessageId,
|
|
1029
|
+
content: getString(sentItem?.raw_message) || getMsgRawTxt(sentItem ?? {}),
|
|
1030
|
+
message: Array.isArray(sentItem?.message) ? sentItem.message : [],
|
|
1031
|
+
source: 'webui',
|
|
1032
|
+
sentAt: Number(sentItem?.local_time ?? sentItem?.timestamp_ms ?? Date.now()),
|
|
1033
|
+
kind: String(sentItem?.message?.[0]?.type ?? 'text'),
|
|
1034
|
+
})
|
|
1035
|
+
}
|
|
1036
|
+
// 请求消息内容
|
|
1037
|
+
// PS:其实有消息通知的情况下不需要再去主动获取了
|
|
1038
|
+
// 但是为了兼容没有开启自身消息通知的情况,还是保留了这个功能
|
|
1039
|
+
Connector.send(
|
|
1040
|
+
authStore.jsonMap.get_message?.name ?? 'get_msg',
|
|
1041
|
+
{ message_id: msg.message_id },
|
|
1042
|
+
'getSendMsg_' + msg.message_id,
|
|
1043
|
+
)
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
sendFileBack: (
|
|
1047
|
+
_: string,
|
|
1048
|
+
msg: { [key: string]: any },
|
|
1049
|
+
echoList: string[],
|
|
1050
|
+
) => {
|
|
1051
|
+
// 标记上传任务完成
|
|
1052
|
+
if (echoList[1] === 'task' && echoList[2] && echoList[3]) {
|
|
1053
|
+
const taskId = echoList[1] + '_' + echoList[2] + '_' + echoList[3]
|
|
1054
|
+
completeUploadTask(taskId)
|
|
1055
|
+
}
|
|
1056
|
+
const newEchoList = ['sendMsgBack', ...echoList.slice(4)]
|
|
1057
|
+
msgFunctions['sendMsgBack'](_, msg, newEchoList)
|
|
1058
|
+
},
|
|
1059
|
+
/**
|
|
1060
|
+
* 获取收藏表情
|
|
1061
|
+
*/
|
|
1062
|
+
getRoamingStamp: (
|
|
1063
|
+
_: string,
|
|
1064
|
+
msg: { [key: string]: any },
|
|
1065
|
+
echoList: string[],
|
|
1066
|
+
) => {
|
|
1067
|
+
const authStore = useAuthStore()
|
|
1068
|
+
const getCount = Number(echoList[1])
|
|
1069
|
+
const data = msg.data
|
|
1070
|
+
if (msgPath.roaming_stamp.reverse) {
|
|
1071
|
+
data.reverse()
|
|
1072
|
+
}
|
|
1073
|
+
const stickerStore = useStickerStore()
|
|
1074
|
+
const stickerCache = stickerStore.stickerCache ?? []
|
|
1075
|
+
if (stickerCache.length == 0) {
|
|
1076
|
+
stickerStore.stickerCache = data
|
|
1077
|
+
} else if (authStore.jsonMap.roaming_stamp.pagerType == 'full') {
|
|
1078
|
+
// 全量分页模式下不追加
|
|
1079
|
+
if (getCount > stickerCache.length + 48) {
|
|
1080
|
+
// 已经获取到所有内容了
|
|
1081
|
+
data.push('end')
|
|
1082
|
+
}
|
|
1083
|
+
stickerStore.stickerCache = data
|
|
1084
|
+
} else {
|
|
1085
|
+
stickerStore.stickerCache = stickerCache.concat(data)
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* 保存群补充信息
|
|
1091
|
+
* @deprecated 功能在后期更新中未被重构检查,可能存在问题
|
|
1092
|
+
*/
|
|
1093
|
+
getMoreGroupInfo: (_: string, msg: { [key: string]: any }) => {
|
|
1094
|
+
const chatStore = useChatStore()
|
|
1095
|
+
chatStore.chatInfo.info.group_info = msg.data.data
|
|
1096
|
+
},
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* 保存好友补充信息
|
|
1100
|
+
* @deprecated 功能在后期更新中未被重构检查,可能存在问题
|
|
1101
|
+
*/
|
|
1102
|
+
getMoreUserInfo: (_: string, msg: { [key: string]: any }) => {
|
|
1103
|
+
const chatStore = useChatStore()
|
|
1104
|
+
// chatStore.chatInfo.info.user_info =
|
|
1105
|
+
// msg.data.data.result.buddy.info_list[0]
|
|
1106
|
+
const data = getMsgData('friend_info', msg, msgPath.friend_info)[0]
|
|
1107
|
+
data.regTime = new Date(data.reg_time).getTime()
|
|
1108
|
+
if (data) {
|
|
1109
|
+
chatStore.chatInfo.info.user_info = data
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* 获取群通知
|
|
1115
|
+
*/
|
|
1116
|
+
getGroupNotices: (_: string, msg: { [key: string]: any }) => {
|
|
1117
|
+
const chatStore = useChatStore()
|
|
1118
|
+
const list = getMsgData('group_notices', msg, msgPath.group_notices)
|
|
1119
|
+
if (!list) return
|
|
1120
|
+
|
|
1121
|
+
// 组装img信息
|
|
1122
|
+
let lastImg: Img | undefined
|
|
1123
|
+
for (const notice of list) {
|
|
1124
|
+
if (!notice.img_id || notice.img_id.length == 0) continue
|
|
1125
|
+
const img = markRaw(new Img(
|
|
1126
|
+
`https://p.qlogo.cn/gdynamic/${notice.img_id}/0/`
|
|
1127
|
+
))
|
|
1128
|
+
if (lastImg) img.insertPrev(lastImg)
|
|
1129
|
+
notice.img = img
|
|
1130
|
+
lastImg = img
|
|
1131
|
+
}
|
|
1132
|
+
chatStore.chatInfo.info.group_notices = list
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* 获取群文件列表
|
|
1137
|
+
*/
|
|
1138
|
+
getGroupFiles: (_: string, msg: { [key: string]: any }) => {
|
|
1139
|
+
const chatStore = useChatStore()
|
|
1140
|
+
const list = getMsgData('group_files', msg, msgPath.group_files) as (GroupFileElem & GroupFileFolderElem)[]
|
|
1141
|
+
// 排序;文件夹在前,文件在后
|
|
1142
|
+
const folderList = list.filter((item) => {
|
|
1143
|
+
return item.folder_id
|
|
1144
|
+
})
|
|
1145
|
+
const fileList = list.filter((item) => {
|
|
1146
|
+
return item.file_id
|
|
1147
|
+
})
|
|
1148
|
+
// 对它们各自排序,文件夹按照 create_time 降序,文件按照 upload_time 降序
|
|
1149
|
+
folderList.sort((a, b) => {
|
|
1150
|
+
return b.create_time - a.create_time
|
|
1151
|
+
})
|
|
1152
|
+
fileList.sort((a, b) => {
|
|
1153
|
+
return b.upload_time - a.upload_time
|
|
1154
|
+
})
|
|
1155
|
+
// 合并
|
|
1156
|
+
chatStore.chatInfo.info.group_files = folderList.concat(fileList)
|
|
1157
|
+
},
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* 获取群文件文件夹文件
|
|
1161
|
+
*/
|
|
1162
|
+
getGroupDirFiles: (_: string, msg: { [key: string]: any }, echoList: string[]) => {
|
|
1163
|
+
const chatStore = useChatStore()
|
|
1164
|
+
// TODO: 有分页
|
|
1165
|
+
|
|
1166
|
+
// 默认使用主目录相同的结构,如果存在子目录结构的定义则使用子目录的结构
|
|
1167
|
+
let map = msgPath.group_files
|
|
1168
|
+
if (msgPath.group_folder_files.source) {
|
|
1169
|
+
map = msgPath.group_folder
|
|
1170
|
+
}
|
|
1171
|
+
const list = getMsgData('group_files', msg, map) as (GroupFileElem & GroupFileFolderElem)[]
|
|
1172
|
+
// 排序;文件夹在前,文件在后
|
|
1173
|
+
const folderList = list.filter((item) => {
|
|
1174
|
+
return item.folder_id
|
|
1175
|
+
})
|
|
1176
|
+
const fileList = list.filter((item) => {
|
|
1177
|
+
return item.file_id
|
|
1178
|
+
})
|
|
1179
|
+
// 对它们各自排序,文件夹按照 create_time 降序,文件按照 upload_time 降序
|
|
1180
|
+
folderList.sort((a, b) => {
|
|
1181
|
+
return b.create_time - a.create_time
|
|
1182
|
+
})
|
|
1183
|
+
fileList.sort((a, b) => {
|
|
1184
|
+
return b.upload_time - a.upload_time
|
|
1185
|
+
})
|
|
1186
|
+
// 寻找 item
|
|
1187
|
+
const folderId = echoList[1]
|
|
1188
|
+
const folder = chatStore.chatInfo.info.group_files.find((item) => {
|
|
1189
|
+
return item.folder_id == folderId
|
|
1190
|
+
})
|
|
1191
|
+
if (folder) {
|
|
1192
|
+
folder.items = fileList
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* 下载文件(聊天中)
|
|
1198
|
+
*/
|
|
1199
|
+
downloadFile: (_: string, msg: { [key: string]: any }, echoList: string[]) => {
|
|
1200
|
+
const data = getMsgData('file_download', msg, msgPath.file_download)[0]
|
|
1201
|
+
const url = data.file_url
|
|
1202
|
+
|
|
1203
|
+
const fileName = decodeURIComponent(atob(echoList[2]))
|
|
1204
|
+
const fileSize = data.file_size || 0
|
|
1205
|
+
|
|
1206
|
+
// 使用文件传输管理器下载
|
|
1207
|
+
addDownloadTask({
|
|
1208
|
+
fileName,
|
|
1209
|
+
fileSize,
|
|
1210
|
+
filePath: '',
|
|
1211
|
+
url
|
|
1212
|
+
})
|
|
1213
|
+
},
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* 下载文件(群文件)
|
|
1217
|
+
*/
|
|
1218
|
+
downloadGroupFile: (_: string, msg: { [key: string]: any }, echoList: string[]) => {
|
|
1219
|
+
const data = getMsgData('file_download', msg, msgPath.file_download)[0]
|
|
1220
|
+
const url = data.file_url
|
|
1221
|
+
|
|
1222
|
+
const fileName = decodeURIComponent(atob(echoList[2]))
|
|
1223
|
+
const fileSize = data.file_size || 0
|
|
1224
|
+
|
|
1225
|
+
// 使用文件传输管理器下载
|
|
1226
|
+
addDownloadTask({
|
|
1227
|
+
fileName,
|
|
1228
|
+
fileSize,
|
|
1229
|
+
filePath: '',
|
|
1230
|
+
url
|
|
1231
|
+
})
|
|
1232
|
+
},
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* 文件预览下载
|
|
1236
|
+
*/
|
|
1237
|
+
loadFileBase: (
|
|
1238
|
+
_: string,
|
|
1239
|
+
msg: { [key: string]: any },
|
|
1240
|
+
echoList: string[],
|
|
1241
|
+
) => {
|
|
1242
|
+
const chatStore = useChatStore()
|
|
1243
|
+
const data = getMsgData('file_download', msg, msgPath.file_download)[0]
|
|
1244
|
+
let url = data.file_url
|
|
1245
|
+
const msgId = echoList[1]
|
|
1246
|
+
const ext = echoList[2]
|
|
1247
|
+
if (url) {
|
|
1248
|
+
// 寻找消息
|
|
1249
|
+
const msg = chatStore.messageList.find((item) => {
|
|
1250
|
+
return item.message_id == msgId
|
|
1251
|
+
})
|
|
1252
|
+
if (msg) {
|
|
1253
|
+
if (document.location.protocol == 'https:') {
|
|
1254
|
+
// 判断文件 URL 的协议
|
|
1255
|
+
// PS:Chrome 不会对 http 文件进行协议升级
|
|
1256
|
+
if (url.toLowerCase().startsWith('http:')) {
|
|
1257
|
+
url = 'https' + url.substring(url.indexOf('://'))
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
msg.fileView.url = url
|
|
1261
|
+
msg.fileView.ext = ext
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* 保存精华消息
|
|
1268
|
+
*/
|
|
1269
|
+
getJin: (_: string, msg: { [key: string]: any }) => {
|
|
1270
|
+
const chatStore = useChatStore()
|
|
1271
|
+
const jinList = getMsgData('group_essence', msg, msgPath.group_essence)
|
|
1272
|
+
const is_end = getMsgData(
|
|
1273
|
+
'is_end',
|
|
1274
|
+
msg,
|
|
1275
|
+
msgPath.group_essence.is_end,
|
|
1276
|
+
) ?? [true]
|
|
1277
|
+
if (jinList && is_end) {
|
|
1278
|
+
if (chatStore.chatInfo.info.jin_info.list.length == 0) {
|
|
1279
|
+
chatStore.chatInfo.info.jin_info.list = jinList
|
|
1280
|
+
} else {
|
|
1281
|
+
const now_page = chatStore.chatInfo.info.jin_info.pages ?? 0
|
|
1282
|
+
|
|
1283
|
+
chatStore.chatInfo.info.jin_info.list =
|
|
1284
|
+
chatStore.chatInfo.info.jin_info.list.concat(jinList)
|
|
1285
|
+
chatStore.chatInfo.info.jin_info.pages = now_page + 1
|
|
1286
|
+
}
|
|
1287
|
+
chatStore.chatInfo.info.jin_info.is_end = is_end[0]
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* 获取发送的消息(消息发送后处理)
|
|
1293
|
+
* @deprecated 功能已被遗弃,暂时保留方法
|
|
1294
|
+
*/
|
|
1295
|
+
getSendMsg: (
|
|
1296
|
+
_: string,
|
|
1297
|
+
msg: { [key: string]: any },
|
|
1298
|
+
echoList: string[],
|
|
1299
|
+
) => {
|
|
1300
|
+
const authStore = useAuthStore()
|
|
1301
|
+
const chatStore = useChatStore()
|
|
1302
|
+
const msgInfo = getMsgData('message_info', msg.data, msgPath.message_info)
|
|
1303
|
+
if (msgInfo) {
|
|
1304
|
+
const info = msgInfo[0]
|
|
1305
|
+
if (echoList[1] !== info.message_id.toString()) {
|
|
1306
|
+
// 返回的不是这条消息,重新请求
|
|
1307
|
+
setTimeout(() => {
|
|
1308
|
+
Connector.send(
|
|
1309
|
+
authStore.jsonMap.get_message?.name ?? 'get_msg',
|
|
1310
|
+
{ message_id: echoList[1] },
|
|
1311
|
+
'getSendMsg_' + echoList[1]
|
|
1312
|
+
)
|
|
1313
|
+
}, 5000)
|
|
1314
|
+
} else {
|
|
1315
|
+
// 列表内最近的一条 fake_msg(倒序查找)
|
|
1316
|
+
let fakeMsg = null as any
|
|
1317
|
+
for (let i = chatStore.messageList.length - 1; i > 0; i--) {
|
|
1318
|
+
const msg = chatStore.messageList[i]
|
|
1319
|
+
if (msg.fake_msg != undefined && info.sender == authStore.loginInfo.uin) {
|
|
1320
|
+
fakeMsg = msg
|
|
1321
|
+
break
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
// 预发送消息刷新
|
|
1325
|
+
if (fakeMsg != null) {
|
|
1326
|
+
// 将这条消息直接替换掉
|
|
1327
|
+
const trueMsg = getMsgData(
|
|
1328
|
+
'message_list',
|
|
1329
|
+
buildMsgList([msg.data]),
|
|
1330
|
+
msgPath.message_list,
|
|
1331
|
+
)
|
|
1332
|
+
getMessageList(trueMsg).then((trueMsg) => {
|
|
1333
|
+
if (trueMsg?.length == 1) {
|
|
1334
|
+
// 使用消息对象引用直接更新,避免索引问题
|
|
1335
|
+
fakeMsg.message = trueMsg[0].message
|
|
1336
|
+
fakeMsg.raw_message = trueMsg[0].raw_message
|
|
1337
|
+
fakeMsg.time = trueMsg[0].time
|
|
1338
|
+
fakeMsg.fake_msg = undefined
|
|
1339
|
+
fakeMsg.revoke = false
|
|
1340
|
+
}
|
|
1341
|
+
})
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1347
|
+
/**
|
|
1348
|
+
* 设置消息已读
|
|
1349
|
+
*/
|
|
1350
|
+
readMemberMessage: (_: string, msg: { [key: string]: any }) => {
|
|
1351
|
+
const authStore = useAuthStore()
|
|
1352
|
+
const data = msg.data[0]
|
|
1353
|
+
const msgName = authStore.jsonMap.set_message_read.private_name
|
|
1354
|
+
let private_name = authStore.jsonMap.set_message_read.private_name
|
|
1355
|
+
if (!private_name) private_name = msgName
|
|
1356
|
+
if (data.group_id != undefined) {
|
|
1357
|
+
Connector.send(
|
|
1358
|
+
msgName,
|
|
1359
|
+
{
|
|
1360
|
+
message_id: data.message_id,
|
|
1361
|
+
group_id: data.group_id,
|
|
1362
|
+
},
|
|
1363
|
+
'setMessageRead',
|
|
1364
|
+
)
|
|
1365
|
+
} else {
|
|
1366
|
+
Connector.send(
|
|
1367
|
+
private_name,
|
|
1368
|
+
{
|
|
1369
|
+
message_id: data.message_id,
|
|
1370
|
+
user_id: data.self_id,
|
|
1371
|
+
},
|
|
1372
|
+
'setMessageRead',
|
|
1373
|
+
)
|
|
1374
|
+
}
|
|
1375
|
+
// 关闭所有通知
|
|
1376
|
+
new Notify().closeAll(data.group_id ?? data.self_id)
|
|
1377
|
+
},
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* 系统通知后处理
|
|
1381
|
+
*/
|
|
1382
|
+
setFriendAdd: updateSysInfo,
|
|
1383
|
+
setGroupAdd: updateSysInfo,
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* 获取会话历史
|
|
1387
|
+
*/
|
|
1388
|
+
getRecentContact: (_: string, data: any) => {
|
|
1389
|
+
const authStore = useAuthStore()
|
|
1390
|
+
const contactStore = useContactStore()
|
|
1391
|
+
const settingsStore = useSettingsStore()
|
|
1392
|
+
const list = getMsgData('recent_contact', data, msgPath.recent_contact)
|
|
1393
|
+
if (list != undefined) {
|
|
1394
|
+
// user_id: /peerUin
|
|
1395
|
+
// time: /msgTime
|
|
1396
|
+
// chat_type: /chatType
|
|
1397
|
+
// 过滤掉 chatType 不是 1 和 2 的
|
|
1398
|
+
let back = list.filter((item) => {
|
|
1399
|
+
return item.chat_type == 1 || item.chat_type == 2
|
|
1400
|
+
})
|
|
1401
|
+
// 排除掉在置顶列表里的
|
|
1402
|
+
const topList = settingsStore.sysConfig.top_info as {
|
|
1403
|
+
[key: string]: number[]
|
|
1404
|
+
} | null
|
|
1405
|
+
if (topList != null) {
|
|
1406
|
+
const top = topList[authStore.loginInfo.uin]
|
|
1407
|
+
if (top != undefined) {
|
|
1408
|
+
back = back.filter((item) => {
|
|
1409
|
+
return top.indexOf(Number(item.user_id)) == -1
|
|
1410
|
+
})
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
// 去重
|
|
1414
|
+
back = back.filter((item, index, arr) => {
|
|
1415
|
+
return (
|
|
1416
|
+
arr.findIndex((item2) => {
|
|
1417
|
+
return item2.user_id == item.user_id
|
|
1418
|
+
}) == index
|
|
1419
|
+
)
|
|
1420
|
+
})
|
|
1421
|
+
back.forEach((item) => {
|
|
1422
|
+
// 去消息列表里找一下它
|
|
1423
|
+
const user = contactStore.userList.find((user) => {
|
|
1424
|
+
return user.user_id == item.user_id || user.group_id == item.user_id
|
|
1425
|
+
})
|
|
1426
|
+
if (user) {
|
|
1427
|
+
contactStore.baseOnMsgList.set(String(item.user_id), user)
|
|
1428
|
+
updateLastestHistory(user)
|
|
1429
|
+
}
|
|
1430
|
+
})
|
|
1431
|
+
}
|
|
1432
|
+
// “显示全部会话”会包含 recent_contact 之外的群;限流补取这些群的最后一条历史。
|
|
1433
|
+
groupPreviewHydrator.scheduleMissingSessions()
|
|
1434
|
+
},
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* 表情回应后处理
|
|
1438
|
+
*/
|
|
1439
|
+
SendRespondBack: (
|
|
1440
|
+
_: string,
|
|
1441
|
+
__: { [key: string]: any },
|
|
1442
|
+
echoList: string[],
|
|
1443
|
+
) => {
|
|
1444
|
+
const chatStore = useChatStore()
|
|
1445
|
+
const msgId = echoList[1]
|
|
1446
|
+
const id = Number(echoList[2])
|
|
1447
|
+
// 从消息列表中找到这条消息
|
|
1448
|
+
chatStore.messageList.forEach((item, index) => {
|
|
1449
|
+
if (item.message_id === msgId) {
|
|
1450
|
+
if (chatStore.messageList[index].emoji_like) {
|
|
1451
|
+
// 寻找有没有 emoji_id 相同的
|
|
1452
|
+
let hasAdd = false
|
|
1453
|
+
chatStore.messageList[index].emoji_like.forEach(
|
|
1454
|
+
(item: { emoji_id: number; count: number }) => {
|
|
1455
|
+
if (item.emoji_id == id) {
|
|
1456
|
+
item.count++
|
|
1457
|
+
hasAdd = true
|
|
1458
|
+
}
|
|
1459
|
+
},
|
|
1460
|
+
)
|
|
1461
|
+
if (!hasAdd) {
|
|
1462
|
+
chatStore.messageList[index].emoji_like.push({
|
|
1463
|
+
emoji_id: id,
|
|
1464
|
+
count: 1,
|
|
1465
|
+
})
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
chatStore.messageList[index].emoji_like = [
|
|
1469
|
+
{ emoji_id: id, count: 1 },
|
|
1470
|
+
]
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
})
|
|
1474
|
+
},
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* 获取 cookie
|
|
1478
|
+
* @deprecated 暂时没用到他
|
|
1479
|
+
*/
|
|
1480
|
+
getCookies: (
|
|
1481
|
+
_: string,
|
|
1482
|
+
msg: { [key: string]: any },
|
|
1483
|
+
echoList: string[],
|
|
1484
|
+
) => {
|
|
1485
|
+
const authStore = useAuthStore()
|
|
1486
|
+
// 拆分 cookie
|
|
1487
|
+
const cookieObject = {} as { [key: string]: string }
|
|
1488
|
+
msg.data.cookies.split('; ').forEach((item: string) => {
|
|
1489
|
+
const key = item.split('=')[0]
|
|
1490
|
+
const value = item.split('=')[1]
|
|
1491
|
+
cookieObject[key] = value
|
|
1492
|
+
})
|
|
1493
|
+
// 计算 bkn
|
|
1494
|
+
const skey = cookieObject['skey'] || ''
|
|
1495
|
+
let hash = 5381
|
|
1496
|
+
|
|
1497
|
+
for (let i = 0; i < skey.length; i++) {
|
|
1498
|
+
hash += (hash << 5) + skey.charCodeAt(i)
|
|
1499
|
+
}
|
|
1500
|
+
// 保存 cookie 和 bkn
|
|
1501
|
+
const domain = echoList[1]
|
|
1502
|
+
if (!authStore.loginInfo.webapi) authStore.loginInfo.webapi = {}
|
|
1503
|
+
if (!authStore.loginInfo.webapi[domain])
|
|
1504
|
+
authStore.loginInfo.webapi[domain] = {}
|
|
1505
|
+
authStore.loginInfo.webapi[domain].cookie = cookieObject
|
|
1506
|
+
authStore.loginInfo.webapi[domain].bkn = (
|
|
1507
|
+
hash & 0x7fffffff
|
|
1508
|
+
).toString()
|
|
1509
|
+
},
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* 设置消息已读回调
|
|
1513
|
+
*/
|
|
1514
|
+
setMessageRead() {
|
|
1515
|
+
// do nothing
|
|
1516
|
+
},
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* 获取 QQ 空间推送列表
|
|
1520
|
+
* @param _
|
|
1521
|
+
* @param msg
|
|
1522
|
+
*/
|
|
1523
|
+
getQzoneFeed: (_: string, msg: { [key: string]: any }) => {
|
|
1524
|
+
const qzoneStore = useQzoneStore()
|
|
1525
|
+
const list = getMsgData('get_qzone_feed', msg, msgPath.get_qzone_feed)
|
|
1526
|
+
if (list) {
|
|
1527
|
+
qzoneStore.state.currentView = 'feed'
|
|
1528
|
+
qzoneStore.qzoneFeedList = list
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* 获取 QQ 空间“我的”列表
|
|
1534
|
+
* @param _
|
|
1535
|
+
* @param msg
|
|
1536
|
+
*/
|
|
1537
|
+
getQzoneMsg: (_: string, msg: { [key: string]: any }) => {
|
|
1538
|
+
const qzoneStore = useQzoneStore()
|
|
1539
|
+
const list = getMsgData('get_qzone_msg', msg, msgPath.get_qzone_msg)
|
|
1540
|
+
if (list) {
|
|
1541
|
+
qzoneStore.state.currentView = 'my'
|
|
1542
|
+
if (qzoneStore.state.myPagePos === 0) {
|
|
1543
|
+
qzoneStore.qzoneFeedList = list
|
|
1544
|
+
} else {
|
|
1545
|
+
qzoneStore.qzoneFeedList = [
|
|
1546
|
+
...qzoneStore.qzoneFeedList,
|
|
1547
|
+
...list,
|
|
1548
|
+
]
|
|
1549
|
+
}
|
|
1550
|
+
qzoneStore.state.myHasMore = list.length >= qzoneStore.state.myPageSize
|
|
1551
|
+
}
|
|
1552
|
+
qzoneStore.state.myLoading = false
|
|
1553
|
+
}
|
|
1554
|
+
} as {
|
|
1555
|
+
[key: string]: (
|
|
1556
|
+
name: string,
|
|
1557
|
+
msg: { [key: string]: any },
|
|
1558
|
+
echoList?: string[],
|
|
1559
|
+
) => void
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const handlers: Record<string, (payload: any, metaArgs?: string[]) => void> = {
|
|
1563
|
+
...(Object.entries(msgFunctions).reduce((acc, [key, fn]) => ({
|
|
1564
|
+
...acc,
|
|
1565
|
+
[key]: (payload: any, metaArgs?: string[]) => fn(key, payload, metaArgs)
|
|
1566
|
+
}), {})),
|
|
1567
|
+
...(Object.entries(noticeFunctions).reduce((acc, [key, fn]) => ({
|
|
1568
|
+
...acc,
|
|
1569
|
+
[key]: (payload: any) => fn(key, payload)
|
|
1570
|
+
}), {}))
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
// ==========================================
|
|
1574
|
+
|
|
1575
|
+
function saveUser(msg: { [key: string]: any }, type: string) {
|
|
1576
|
+
const authStore = useAuthStore()
|
|
1577
|
+
const contactStore = useContactStore()
|
|
1578
|
+
const settingsStore = useSettingsStore()
|
|
1579
|
+
listLoadTimes++
|
|
1580
|
+
let list: any[] | undefined
|
|
1581
|
+
const payload = getDataObject(msg)
|
|
1582
|
+
if (Array.isArray(payload.data)) {
|
|
1583
|
+
list = payload.data as any[]
|
|
1584
|
+
} else if (msgPath.user_list) {
|
|
1585
|
+
list = getMsgData('user_list', msg, msgPath.user_list)
|
|
1586
|
+
} else {
|
|
1587
|
+
switch (type) {
|
|
1588
|
+
case 'friend':
|
|
1589
|
+
list = getMsgData('friend_list', msg, msgPath.friend_list)
|
|
1590
|
+
if (list)
|
|
1591
|
+
// 根据 user_id 去重
|
|
1592
|
+
list = list.filter((item, index, arr) => {
|
|
1593
|
+
return (
|
|
1594
|
+
arr.findIndex((item2) => {
|
|
1595
|
+
return item2.user_id == item.user_id
|
|
1596
|
+
}) == index
|
|
1597
|
+
)
|
|
1598
|
+
})
|
|
1599
|
+
break
|
|
1600
|
+
case 'group':
|
|
1601
|
+
list = getMsgData('group_list', msg, msgPath.group_list)
|
|
1602
|
+
if (list)
|
|
1603
|
+
// 根据 group_id 去重
|
|
1604
|
+
list = list.filter((item, index, arr) => {
|
|
1605
|
+
return (
|
|
1606
|
+
arr.findIndex((item2) => {
|
|
1607
|
+
return item2.group_id == item.group_id
|
|
1608
|
+
}) == index
|
|
1609
|
+
)
|
|
1610
|
+
})
|
|
1611
|
+
break
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (list != undefined) {
|
|
1615
|
+
list = list.filter((item) => {
|
|
1616
|
+
const id = item.user_id ?? item.group_id
|
|
1617
|
+
return id !== undefined && id !== null && String(id) !== '0'
|
|
1618
|
+
})
|
|
1619
|
+
}
|
|
1620
|
+
if (list != undefined) {
|
|
1621
|
+
const groupNames = {} as { [key: number]: string }
|
|
1622
|
+
list.forEach((item, index) => {
|
|
1623
|
+
if (item.group_name == null || item.group_name == undefined) {
|
|
1624
|
+
item.group_name = ''
|
|
1625
|
+
}
|
|
1626
|
+
if (list?.[index]) {
|
|
1627
|
+
list[index].py_name = { main: [], short: [] }
|
|
1628
|
+
list[index].py_start = ' '
|
|
1629
|
+
}
|
|
1630
|
+
// 构建分类
|
|
1631
|
+
if (type == 'friend') {
|
|
1632
|
+
if (item.class_id != undefined && item.class_name) {
|
|
1633
|
+
if (typeof item.class_name == 'string') {
|
|
1634
|
+
groupNames[item.class_id] = item.class_name
|
|
1635
|
+
} else {
|
|
1636
|
+
groupNames[item.class_id] = item.class_name[0]
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
delete item.group_name
|
|
1640
|
+
} else {
|
|
1641
|
+
delete item.class_id
|
|
1642
|
+
delete item.class_name
|
|
1643
|
+
}
|
|
1644
|
+
})
|
|
1645
|
+
if (Object.keys(groupNames).length > 0) {
|
|
1646
|
+
// 把 groupNames 处理为 { class_id: number, class_name: string }[]
|
|
1647
|
+
const groupNamesList = [] as {
|
|
1648
|
+
class_id: number
|
|
1649
|
+
class_name: string
|
|
1650
|
+
}[]
|
|
1651
|
+
for (const key in groupNames) {
|
|
1652
|
+
groupNamesList.push({
|
|
1653
|
+
class_id: Number(key),
|
|
1654
|
+
class_name: groupNames[key],
|
|
1655
|
+
})
|
|
1656
|
+
}
|
|
1657
|
+
saveClassInfo(groupNamesList)
|
|
1658
|
+
} else if (type == 'friend') {
|
|
1659
|
+
// Satori 的好友接口不返回分组时,统一归入“我的好友”
|
|
1660
|
+
settingsStore.classes = [{
|
|
1661
|
+
class_id: 0,
|
|
1662
|
+
class_name: '我的好友',
|
|
1663
|
+
sort_id: 0,
|
|
1664
|
+
}]
|
|
1665
|
+
}
|
|
1666
|
+
if (isPinyinReady()) {
|
|
1667
|
+
buildPinyinForContacts(list)
|
|
1668
|
+
} else {
|
|
1669
|
+
hydrateContactPinyinLater(list)
|
|
1670
|
+
}
|
|
1671
|
+
sortContactListByPinyin(list)
|
|
1672
|
+
// 实时消息可能比联系人列表更早到达;用真实联系人资料接管临时会话,保留预览状态。
|
|
1673
|
+
const didMergeEarlySessions = mergeEarlySessionContacts(
|
|
1674
|
+
list,
|
|
1675
|
+
contactStore.baseOnMsgList,
|
|
1676
|
+
)
|
|
1677
|
+
const existingIds = new Set(
|
|
1678
|
+
contactStore.userList.map((item) => {
|
|
1679
|
+
return String(item.user_id ?? item.group_id ?? '')
|
|
1680
|
+
}),
|
|
1681
|
+
)
|
|
1682
|
+
const freshList = list.filter((item) => {
|
|
1683
|
+
return !existingIds.has(String(item.user_id ?? item.group_id ?? ''))
|
|
1684
|
+
})
|
|
1685
|
+
contactStore.userList = contactStore.userList.concat(freshList)
|
|
1686
|
+
if (
|
|
1687
|
+
settingsStore.sysConfig.session_display_mode === 'all' ||
|
|
1688
|
+
didMergeEarlySessions
|
|
1689
|
+
) {
|
|
1690
|
+
updateBaseOnMsgList()
|
|
1691
|
+
}
|
|
1692
|
+
// 刷新置顶列表
|
|
1693
|
+
const info = settingsStore.sysConfig.top_info as {
|
|
1694
|
+
[key: string]: number[]
|
|
1695
|
+
} | null
|
|
1696
|
+
if (info != null) {
|
|
1697
|
+
const topList = info[authStore.loginInfo.uin]
|
|
1698
|
+
if (topList !== undefined) {
|
|
1699
|
+
list.forEach((item) => {
|
|
1700
|
+
const id = Number(
|
|
1701
|
+
item.user_id ? item.user_id : item.group_id,
|
|
1702
|
+
)
|
|
1703
|
+
if (topList.indexOf(id) >= 0) {
|
|
1704
|
+
item.always_top = true
|
|
1705
|
+
// 判断它在不在消息列表里
|
|
1706
|
+
if (contactStore.baseOnMsgList.get(normalizeSessionId(id)) == undefined) {
|
|
1707
|
+
contactStore.baseOnMsgList.set(normalizeSessionId(id), item)
|
|
1708
|
+
// 给它获取一下最新的一条消息
|
|
1709
|
+
// 给置顶的用户刷新最新一条的消息用于显示
|
|
1710
|
+
contactStore.userList.forEach((item) => {
|
|
1711
|
+
if (item.always_top) {
|
|
1712
|
+
updateLastestHistory(item)
|
|
1713
|
+
}
|
|
1714
|
+
})
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
})
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
// 更新菜单
|
|
1721
|
+
updateMenu({
|
|
1722
|
+
parent: 'account',
|
|
1723
|
+
id: 'userList',
|
|
1724
|
+
action: 'label',
|
|
1725
|
+
value: app.config.globalProperties.$t('用户列表({count})', {
|
|
1726
|
+
count: contactStore.userList.length,
|
|
1727
|
+
}),
|
|
1728
|
+
})
|
|
1729
|
+
}
|
|
1730
|
+
// 如果获取次数大于 0 并且是双数,刷新一下历史会话
|
|
1731
|
+
if (listLoadTimes > 0 && listLoadTimes % 2 == 0) {
|
|
1732
|
+
// 获取最近的会话
|
|
1733
|
+
if (authStore.jsonMap.recent_contact)
|
|
1734
|
+
Connector.send(
|
|
1735
|
+
authStore.jsonMap.recent_contact.name,
|
|
1736
|
+
{},
|
|
1737
|
+
'getRecentContact',
|
|
1738
|
+
)
|
|
1739
|
+
}
|
|
1740
|
+
// 如果是分离式的好友列表,继续获取分类信息
|
|
1741
|
+
if (type == 'friend' && authStore.jsonMap.friend_category) {
|
|
1742
|
+
Connector.send(
|
|
1743
|
+
authStore.jsonMap.friend_category.name,
|
|
1744
|
+
{},
|
|
1745
|
+
'getFriendCategory',
|
|
1746
|
+
)
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function saveClassInfo(
|
|
1751
|
+
list: { class_id: number; class_name: string; sort_id?: number }[],
|
|
1752
|
+
) {
|
|
1753
|
+
const settingsStore = useSettingsStore()
|
|
1754
|
+
if (list[0].sort_id != undefined) {
|
|
1755
|
+
// 如果有 sort_id,按 sort_id 排序,从小到大
|
|
1756
|
+
list.sort((a, b) => {
|
|
1757
|
+
if (a.sort_id && b.sort_id) return a.sort_id - b.sort_id
|
|
1758
|
+
else return 0
|
|
1759
|
+
})
|
|
1760
|
+
} else {
|
|
1761
|
+
// 按 class_id 排序
|
|
1762
|
+
list.sort((a, b) => {
|
|
1763
|
+
return a.class_id - b.class_id
|
|
1764
|
+
})
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
settingsStore.classes = list
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
async function saveMsg(msg: any, append = undefined as undefined | string) {
|
|
1771
|
+
const uiStore = useUIStore()
|
|
1772
|
+
const authStore = useAuthStore()
|
|
1773
|
+
const chatStore = useChatStore()
|
|
1774
|
+
const contactStore = useContactStore()
|
|
1775
|
+
const settingsStore = useSettingsStore()
|
|
1776
|
+
let list = await normalizeMessagesFromPayload(msg)
|
|
1777
|
+
if (list != undefined) {
|
|
1778
|
+
const historyBeforeTime = Number(uiStore.historyBeforeTime)
|
|
1779
|
+
const hasHistoryBeforeTime = Number.isFinite(historyBeforeTime)
|
|
1780
|
+
|
|
1781
|
+
// 检查消息是否是当前聊天的消息
|
|
1782
|
+
const firstMsg = list[0]
|
|
1783
|
+
const infoList = getMsgData(
|
|
1784
|
+
'message_info',
|
|
1785
|
+
firstMsg,
|
|
1786
|
+
msgPath.message_info,
|
|
1787
|
+
)
|
|
1788
|
+
if (infoList != undefined) {
|
|
1789
|
+
const info = infoList[0]
|
|
1790
|
+
const id = info.group_id ?? info.private_id
|
|
1791
|
+
if (id != undefined && id != chatStore.chatInfo.show.id) {
|
|
1792
|
+
return
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
// 将消息中 message 字段为空数组的消息过滤掉
|
|
1796
|
+
list = list.filter((item: any) => {
|
|
1797
|
+
return item.message.length > 0
|
|
1798
|
+
})
|
|
1799
|
+
|
|
1800
|
+
// 上拉历史时按时间戳作为边界(兼容增量/全量两种分页模式)。
|
|
1801
|
+
if (hasHistoryBeforeTime && append === 'top') {
|
|
1802
|
+
list = list.filter((item: any) => {
|
|
1803
|
+
const t = Number(item?.time)
|
|
1804
|
+
return Number.isFinite(t) && t <= historyBeforeTime
|
|
1805
|
+
})
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// 处于上拉边界过滤时,若结果为空则保留当前列表,避免误清空。
|
|
1809
|
+
if (hasHistoryBeforeTime && append === 'top' && list.length < 1) {
|
|
1810
|
+
uiStore.historyBeforeTime = undefined
|
|
1811
|
+
uiStore.nowGetHistory = false
|
|
1812
|
+
return
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// 保存到本地历史
|
|
1816
|
+
saveMessagesWithSideEffects(authStore.loginInfo.uin, list)
|
|
1817
|
+
// 如果分页不是增量的,就不使用追加
|
|
1818
|
+
if (
|
|
1819
|
+
append == 'top' &&
|
|
1820
|
+
authStore.jsonMap.message_list?.pagerType == 'full'
|
|
1821
|
+
) {
|
|
1822
|
+
append = undefined
|
|
1823
|
+
}
|
|
1824
|
+
// 追加处理
|
|
1825
|
+
if (append != undefined) {
|
|
1826
|
+
// 没有更旧的消息能加载了,禁用允许加载标志
|
|
1827
|
+
if (list.length < 1) {
|
|
1828
|
+
uiStore.canLoadHistory = false
|
|
1829
|
+
uiStore.historyBeforeTime = undefined
|
|
1830
|
+
return
|
|
1831
|
+
}
|
|
1832
|
+
const merged = mergeMessagesByIdAndTime(chatStore.messageList, list)
|
|
1833
|
+
replaceMessageListInPlace(merged)
|
|
1834
|
+
} else {
|
|
1835
|
+
if (
|
|
1836
|
+
settingsStore.sysConfig.enable_local_history &&
|
|
1837
|
+
settingsStore.sysConfig.mixed_load_messages !== false
|
|
1838
|
+
) {
|
|
1839
|
+
const merged = mergeMessagesByIdAndTime(chatStore.messageList, list)
|
|
1840
|
+
replaceMessageListInPlace(merged)
|
|
1841
|
+
} else {
|
|
1842
|
+
replaceMessageListInPlace(list)
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
// 消息后处理
|
|
1846
|
+
// PS: 部分消息类型可能需要获取附加内容,在此处进行处理
|
|
1847
|
+
chatStore.messageList.forEach((item) => {
|
|
1848
|
+
sendMsgAppendInfo(item)
|
|
1849
|
+
})
|
|
1850
|
+
// 将最新消息同步到会话列表;通过会话 Map 更新以触发 shallowRef 列表刷新。
|
|
1851
|
+
const lastMsg =
|
|
1852
|
+
chatStore.messageList[chatStore.messageList.length - 1]
|
|
1853
|
+
if (lastMsg) {
|
|
1854
|
+
const user = contactStore.userList.find((item) => {
|
|
1855
|
+
return (
|
|
1856
|
+
item.group_id == chatStore.chatInfo.show.id ||
|
|
1857
|
+
item.user_id == chatStore.chatInfo.show.id
|
|
1858
|
+
)
|
|
1859
|
+
})
|
|
1860
|
+
const sessionId = normalizeSessionId(chatStore.chatInfo.show.id)
|
|
1861
|
+
const session = contactStore.baseOnMsgList.get(sessionId) ?? user
|
|
1862
|
+
if (session) {
|
|
1863
|
+
const preview = formatMessageData(
|
|
1864
|
+
lastMsg,
|
|
1865
|
+
chatStore.chatInfo.show.type == 'group',
|
|
1866
|
+
)
|
|
1867
|
+
if (user) Object.assign(user, preview)
|
|
1868
|
+
Object.assign(session, preview)
|
|
1869
|
+
contactStore.baseOnMsgList.set(sessionId, session)
|
|
1870
|
+
updateBaseOnMsgList()
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
if (hasHistoryBeforeTime) {
|
|
1875
|
+
uiStore.historyBeforeTime = undefined
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
async function normalizeMessagesFromPayload(payload: any): Promise<any[] | undefined> {
|
|
1881
|
+
const rawList = getMsgData('message_list', payload, msgPath.message_list)
|
|
1882
|
+
return getMessageList(rawList)
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
export async function normalizeMessagesForPreview(payload: any): Promise<any[]> {
|
|
1886
|
+
const authStore = useAuthStore()
|
|
1887
|
+
const map = authStore.jsonMap
|
|
1888
|
+
|
|
1889
|
+
if (
|
|
1890
|
+
payload &&
|
|
1891
|
+
payload.post_type === 'message' &&
|
|
1892
|
+
Array.isArray(payload.message)
|
|
1893
|
+
) {
|
|
1894
|
+
const directList = parseMsgList(
|
|
1895
|
+
[payload],
|
|
1896
|
+
map?.message_list?.type ?? '$',
|
|
1897
|
+
map?.message_value,
|
|
1898
|
+
)
|
|
1899
|
+
|
|
1900
|
+
if (directList.length === 0) return []
|
|
1901
|
+
return Promise.all(directList.map((item) => msgPreprocess(item)))
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
if (!map?.message_list) return []
|
|
1905
|
+
|
|
1906
|
+
let rawList = getMsgData('message_list', payload, map.message_list)
|
|
1907
|
+
if (rawList == undefined) {
|
|
1908
|
+
rawList = getMsgData(
|
|
1909
|
+
'message_list',
|
|
1910
|
+
buildMsgList([payload]),
|
|
1911
|
+
map.message_list,
|
|
1912
|
+
)
|
|
1913
|
+
}
|
|
1914
|
+
if (rawList == undefined) return []
|
|
1915
|
+
|
|
1916
|
+
const list = parseMsgList(
|
|
1917
|
+
rawList,
|
|
1918
|
+
map.message_list.type,
|
|
1919
|
+
map.message_value,
|
|
1920
|
+
)
|
|
1921
|
+
if (list.length === 0) return []
|
|
1922
|
+
|
|
1923
|
+
if (map.message_list.order === 'reverse') {
|
|
1924
|
+
list.reverse()
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
list.forEach((item: any) => {
|
|
1928
|
+
if (!item.post_type) {
|
|
1929
|
+
item.post_type = 'message'
|
|
1930
|
+
}
|
|
1931
|
+
})
|
|
1932
|
+
|
|
1933
|
+
return Promise.all(list.map((item) => msgPreprocess(item)))
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function normalizeNewIncomingMessage(data: any): any[] {
|
|
1937
|
+
let list = getMsgData(
|
|
1938
|
+
'message_list',
|
|
1939
|
+
buildMsgList([data]),
|
|
1940
|
+
msgPath.message_list,
|
|
1941
|
+
)
|
|
1942
|
+
|
|
1943
|
+
if (list == undefined) return []
|
|
1944
|
+
|
|
1945
|
+
list = parseMsgList(
|
|
1946
|
+
list,
|
|
1947
|
+
msgPath.message_list.type,
|
|
1948
|
+
msgPath.message_value,
|
|
1949
|
+
)
|
|
1950
|
+
return list
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
function insertHistorySegmentAtAnchor(
|
|
1954
|
+
current: any[],
|
|
1955
|
+
anchorMsgId: string,
|
|
1956
|
+
segment: any[],
|
|
1957
|
+
): any[] {
|
|
1958
|
+
const insertIdx = current.findIndex((m) => m.message_id === anchorMsgId)
|
|
1959
|
+
if (insertIdx === -1) return current
|
|
1960
|
+
|
|
1961
|
+
const existingIds = new Set(current.map((m) => normalizeMessageId(m.message_id)))
|
|
1962
|
+
const newMsgs = segment.filter((m) => !existingIds.has(normalizeMessageId(m.message_id)))
|
|
1963
|
+
if (newMsgs.length === 0) return current
|
|
1964
|
+
|
|
1965
|
+
const merged = [
|
|
1966
|
+
...current.slice(0, insertIdx),
|
|
1967
|
+
...newMsgs,
|
|
1968
|
+
...current.slice(insertIdx),
|
|
1969
|
+
]
|
|
1970
|
+
return mergeMessagesByIdAndTime([], merged)
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function normalizeMessageId(id: unknown): string {
|
|
1974
|
+
if (id === null || id === undefined) return ''
|
|
1975
|
+
return String(id)
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
function getMessageTimestamp(msg: any): number {
|
|
1979
|
+
const t = Number(msg?.time)
|
|
1980
|
+
return Number.isFinite(t) ? t : 0
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
function buildFallbackMessageKey(msg: any): string {
|
|
1984
|
+
const local = msg?.time_ms ?? msg?.local_time ?? msg?.timestamp_ms ?? ''
|
|
1985
|
+
const seq = local || (msg?.message_seq ?? msg?.seq_id ?? msg?.seq ?? msg?.sn ?? '')
|
|
1986
|
+
const sender = msg?.sender?.user_id ?? msg?.user_id ?? msg?.sender_id ?? ''
|
|
1987
|
+
const ts = getMessageTimestamp(msg)
|
|
1988
|
+
return `${ts}|${sender}|${seq}`
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function compareMessageOrder(a: any, b: any): number {
|
|
1992
|
+
const localA = Number(a?.time_ms ?? a?.local_time ?? a?.timestamp_ms ?? 0)
|
|
1993
|
+
const localB = Number(b?.time_ms ?? b?.local_time ?? b?.timestamp_ms ?? 0)
|
|
1994
|
+
if (localA > 0 && localB > 0 && localA !== localB) {
|
|
1995
|
+
return localA - localB
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
const ta = getMessageTimestamp(a)
|
|
1999
|
+
const tb = getMessageTimestamp(b)
|
|
2000
|
+
if (ta !== tb) return ta - tb
|
|
2001
|
+
|
|
2002
|
+
const sa = Number(a?.message_seq ?? a?.seq_id ?? a?.seq ?? a?.sn)
|
|
2003
|
+
const sb = Number(b?.message_seq ?? b?.seq_id ?? b?.seq ?? b?.sn)
|
|
2004
|
+
if (Number.isFinite(sa) && Number.isFinite(sb) && sa !== sb) {
|
|
2005
|
+
return sa - sb
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
const ia = normalizeMessageId(a?.message_id)
|
|
2009
|
+
const ib = normalizeMessageId(b?.message_id)
|
|
2010
|
+
if (ia === ib) return 0
|
|
2011
|
+
const na = Number(ia)
|
|
2012
|
+
const nb = Number(ib)
|
|
2013
|
+
if (Number.isFinite(na) && Number.isFinite(nb) && na !== nb) {
|
|
2014
|
+
return na - nb
|
|
2015
|
+
}
|
|
2016
|
+
// 同秒且没有可靠序列时保持平台返回顺序,避免按随机 message_id 打乱
|
|
2017
|
+
return 0
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
function getImageSegments(msg: any): any[] {
|
|
2021
|
+
if (!Array.isArray(msg?.message)) return []
|
|
2022
|
+
return msg.message.filter((seg: any) => seg?.type === 'image')
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function hasImageMessage(msg: any): boolean {
|
|
2026
|
+
return getImageSegments(msg).length > 0
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function hasResolvableImageSource(msg: any): boolean {
|
|
2030
|
+
const imgs = getImageSegments(msg)
|
|
2031
|
+
if (imgs.length === 0) return false
|
|
2032
|
+
return imgs.every((seg: any) => {
|
|
2033
|
+
const url = typeof seg?.url === 'string' ? seg.url : ''
|
|
2034
|
+
const file = typeof seg?.file === 'string' ? seg.file : ''
|
|
2035
|
+
if (url.length > 0) return true
|
|
2036
|
+
if (file.length > 0) return true
|
|
2037
|
+
return false
|
|
2038
|
+
})
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function shouldReplaceDuplicateMessage(existing: any, incoming: any): boolean {
|
|
2042
|
+
const settingsStore = useSettingsStore()
|
|
2043
|
+
if (!hasImageMessage(incoming)) return false
|
|
2044
|
+
if (existing?._from_local_db !== true) return false
|
|
2045
|
+
|
|
2046
|
+
// 关闭本地图片缓存时,在线同 id 图片消息应覆盖本地消息。
|
|
2047
|
+
if (settingsStore.sysConfig.disable_local_history_image_cache === true) {
|
|
2048
|
+
return true
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// 图片缓存开启但本地消息图片字段不完整时,也允许在线覆盖修复。
|
|
2052
|
+
return !hasResolvableImageSource(existing) && hasResolvableImageSource(incoming)
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function mergeMessagesByIdAndTime(current: any[], incoming: any[]): any[] {
|
|
2056
|
+
if (incoming.length === 0) return [...current]
|
|
2057
|
+
if (current.length === 0) {
|
|
2058
|
+
const firstPass = [...incoming]
|
|
2059
|
+
firstPass.sort(compareMessageOrder)
|
|
2060
|
+
return firstPass
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
const idSet = new Set<string>()
|
|
2064
|
+
const idIndexMap = new Map<string, number>()
|
|
2065
|
+
const fallbackSet = new Set<string>()
|
|
2066
|
+
const merged = [] as any[]
|
|
2067
|
+
|
|
2068
|
+
for (const msg of current) {
|
|
2069
|
+
merged.push(msg)
|
|
2070
|
+
const id = normalizeMessageId(msg?.message_id)
|
|
2071
|
+
if (id) {
|
|
2072
|
+
idSet.add(id)
|
|
2073
|
+
idIndexMap.set(id, merged.length - 1)
|
|
2074
|
+
} else {
|
|
2075
|
+
fallbackSet.add(buildFallbackMessageKey(msg))
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
for (const msg of incoming) {
|
|
2080
|
+
const id = normalizeMessageId(msg?.message_id)
|
|
2081
|
+
if (id) {
|
|
2082
|
+
if (idSet.has(id)) {
|
|
2083
|
+
const idx = idIndexMap.get(id)
|
|
2084
|
+
if (idx !== undefined && shouldReplaceDuplicateMessage(merged[idx], msg)) {
|
|
2085
|
+
merged[idx] = msg
|
|
2086
|
+
}
|
|
2087
|
+
continue
|
|
2088
|
+
}
|
|
2089
|
+
idSet.add(id)
|
|
2090
|
+
merged.push(msg)
|
|
2091
|
+
idIndexMap.set(id, merged.length - 1)
|
|
2092
|
+
continue
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
const fallbackKey = buildFallbackMessageKey(msg)
|
|
2096
|
+
if (fallbackSet.has(fallbackKey)) continue
|
|
2097
|
+
fallbackSet.add(fallbackKey)
|
|
2098
|
+
merged.push(msg)
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
merged.sort(compareMessageOrder)
|
|
2102
|
+
return merged
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function replaceMessageListInPlace(next: any[]) {
|
|
2106
|
+
const chatStore = useChatStore()
|
|
2107
|
+
chatStore.messageList.splice(0, chatStore.messageList.length, ...next)
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
export async function getMessageList(
|
|
2111
|
+
list: any[] | undefined,
|
|
2112
|
+
options?: { platform?: string; selfId?: string },
|
|
2113
|
+
) {
|
|
2114
|
+
if (!list) return undefined
|
|
2115
|
+
|
|
2116
|
+
list = parseMsgList(
|
|
2117
|
+
list,
|
|
2118
|
+
msgPath.message_list.type,
|
|
2119
|
+
msgPath.message_value,
|
|
2120
|
+
)
|
|
2121
|
+
list.forEach((item) => {
|
|
2122
|
+
if (Array.isArray(item.message)) item.message = splitSatoriMarkupText(item.message)
|
|
2123
|
+
})
|
|
2124
|
+
// 倒序处理
|
|
2125
|
+
if (msgPath.message_list.order === 'reverse') {
|
|
2126
|
+
list.reverse()
|
|
2127
|
+
}
|
|
2128
|
+
list.sort(compareMessageOrder)
|
|
2129
|
+
// 检查必要字段
|
|
2130
|
+
list.forEach((item: any) => {
|
|
2131
|
+
if (!item.post_type) {
|
|
2132
|
+
item.post_type = 'message'
|
|
2133
|
+
}
|
|
2134
|
+
})
|
|
2135
|
+
const authStore = useAuthStore()
|
|
2136
|
+
const platform = String(options?.platform || authStore.loginInfo.platform || '')
|
|
2137
|
+
const selfId = String(options?.selfId || authStore.loginInfo.uin || '')
|
|
2138
|
+
list.forEach((item: any) => {
|
|
2139
|
+
const sender = item.sender
|
|
2140
|
+
const userId = String(sender?.user_id ?? item.user_id ?? item.sender_id ?? '')
|
|
2141
|
+
if (!userId) return
|
|
2142
|
+
const avatar = getCachedUserAvatar(platform, selfId, userId)
|
|
2143
|
+
if (avatar && sender) {
|
|
2144
|
+
sender.avatar = avatar
|
|
2145
|
+
} else {
|
|
2146
|
+
requestUserAvatar(platform, selfId, userId, item)
|
|
2147
|
+
}
|
|
2148
|
+
})
|
|
2149
|
+
return Promise.all(list.map((item) => msgPreprocess(item, options)))
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
/**
|
|
2153
|
+
* 解析合并转发内容;实时消息和点击卡片都走这里,避免各自维护一套逻辑
|
|
2154
|
+
*/
|
|
2155
|
+
export async function resolveForwardMessageContent(
|
|
2156
|
+
segment: { id?: unknown; content?: unknown },
|
|
2157
|
+
options?: { platform?: string; selfId?: string; channelId?: string },
|
|
2158
|
+
): Promise<unknown[] | undefined> {
|
|
2159
|
+
const authStore = useAuthStore()
|
|
2160
|
+
const platform = String(options?.platform || authStore.loginInfo.platform || '')
|
|
2161
|
+
const selfId = String(options?.selfId || authStore.loginInfo.uin || '')
|
|
2162
|
+
if (Array.isArray(segment.content) && segment.content.length > 0) {
|
|
2163
|
+
return getMessageList(segment.content, options)
|
|
2164
|
+
}
|
|
2165
|
+
if (!segment.id) return undefined
|
|
2166
|
+
const originData = await fetchForwardMessage(
|
|
2167
|
+
platform,
|
|
2168
|
+
selfId,
|
|
2169
|
+
String(segment.id),
|
|
2170
|
+
options?.channelId,
|
|
2171
|
+
)
|
|
2172
|
+
if (!Array.isArray(originData)) return undefined
|
|
2173
|
+
const normalized = originData.map((raw) => {
|
|
2174
|
+
const item = typeof raw === 'object' && raw !== null
|
|
2175
|
+
? raw as Record<string, unknown>
|
|
2176
|
+
: {}
|
|
2177
|
+
const rawSender = typeof item.sender === 'object' && item.sender !== null
|
|
2178
|
+
? item.sender as Record<string, unknown>
|
|
2179
|
+
: {}
|
|
2180
|
+
const userId = String(rawSender.user_id ?? item.user_id ?? '')
|
|
2181
|
+
const nickname = String(rawSender.nickname ?? rawSender.name ?? item.nickname ?? '')
|
|
2182
|
+
const content = item.content ?? item.message
|
|
2183
|
+
const base = {
|
|
2184
|
+
...item,
|
|
2185
|
+
user_id: userId,
|
|
2186
|
+
nickname,
|
|
2187
|
+
sender: { user_id: userId, nickname },
|
|
2188
|
+
}
|
|
2189
|
+
if (typeof content === 'string') {
|
|
2190
|
+
const parsed = parseCQ({ message: content }) as { message?: unknown }
|
|
2191
|
+
return { ...base, message: parsed.message ?? [] }
|
|
2192
|
+
}
|
|
2193
|
+
return base
|
|
2194
|
+
})
|
|
2195
|
+
return getMessageList(normalized, options)
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/**
|
|
2199
|
+
* 消息预处理
|
|
2200
|
+
* @param msg 要处理的消息
|
|
2201
|
+
*/
|
|
2202
|
+
export async function msgPreprocess(
|
|
2203
|
+
msg: any,
|
|
2204
|
+
options?: { platform?: string; selfId?: string },
|
|
2205
|
+
): Promise<any> {
|
|
2206
|
+
if (!Array.isArray(msg.message)) return msg
|
|
2207
|
+
msg.message = splitSatoriMarkupText(msg.message)
|
|
2208
|
+
//#region == json 合并转发 ============================
|
|
2209
|
+
if (msg.message.at(0)?.type === 'json') {
|
|
2210
|
+
try {
|
|
2211
|
+
const data = JSON.parse(msg.message.at(0).data)
|
|
2212
|
+
if (data['app'] === 'com.tencent.multimsg') {
|
|
2213
|
+
msg.message = [{
|
|
2214
|
+
type: 'forward',
|
|
2215
|
+
id: data['meta']['detail']['resid'],
|
|
2216
|
+
}]
|
|
2217
|
+
}
|
|
2218
|
+
} catch (e) {/**/ }
|
|
2219
|
+
}
|
|
2220
|
+
//#endregion
|
|
2221
|
+
|
|
2222
|
+
//#region == 合并转发解析 ==============================
|
|
2223
|
+
if (msg.message.at(0)?.type === 'forward') {
|
|
2224
|
+
try {
|
|
2225
|
+
const data = await resolveForwardMessageContent(msg.message.at(0), options)
|
|
2226
|
+
msg.message.at(0).content = data && data.length > 0 ? data : undefined
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
logger.error(e as unknown as Error, '合并转发解析失败')
|
|
2229
|
+
msg.message.at(0).content = undefined
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
//#endregion
|
|
2233
|
+
|
|
2234
|
+
//#region == lgr 商场表情 =============================
|
|
2235
|
+
// 过滤掉mface后面尾随的字符串
|
|
2236
|
+
const filter: any[] = []
|
|
2237
|
+
for (let id = 0; id < msg.message.length; id++) {
|
|
2238
|
+
const seg = msg.message[id]
|
|
2239
|
+
filter.push(seg)
|
|
2240
|
+
if (seg.type === 'mface') id++
|
|
2241
|
+
}
|
|
2242
|
+
msg.message = filter
|
|
2243
|
+
//#endregion
|
|
2244
|
+
return msg
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
function revokeMsg(_: string, msg: any) {
|
|
2248
|
+
const authStore = useAuthStore()
|
|
2249
|
+
const chatStore = useChatStore()
|
|
2250
|
+
// 清除通知
|
|
2251
|
+
const chatId = msg.notice_type.includes('group') ? msg.group_id : msg.user_id
|
|
2252
|
+
new Notify().closeAll(chatId)
|
|
2253
|
+
|
|
2254
|
+
// 在本地 DB 中标记撤回
|
|
2255
|
+
const msgId = msg.message_id
|
|
2256
|
+
dbRevokeMessage(authStore.loginInfo.uin, String(msgId))
|
|
2257
|
+
|
|
2258
|
+
// 寻找消息
|
|
2259
|
+
let msgGet = null as { [key: string]: any } | null
|
|
2260
|
+
for (const [index, msg] of chatStore.messageList.entries()) {
|
|
2261
|
+
if (msg.message_id === msgId) {
|
|
2262
|
+
msgGet = msg
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
if (!msgGet) {
|
|
2267
|
+
logger.add(LogType.UI, '没有找到这条被撤回的消息')
|
|
2268
|
+
return
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// 不删除消息,保留原文并标记为已撤回。
|
|
2272
|
+
msgGet.revoke = true
|
|
2273
|
+
msgGet.revoked = true
|
|
2274
|
+
msgGet.revokeTime = Date.now() / 1000
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
let qed_try_times = 0
|
|
2278
|
+
function newMsg(_: string, data: any) {
|
|
2279
|
+
const { $t } = app.config.globalProperties
|
|
2280
|
+
const authStore = useAuthStore()
|
|
2281
|
+
const uiStore = useUIStore()
|
|
2282
|
+
const chatStore = useChatStore()
|
|
2283
|
+
const contactStore = useContactStore()
|
|
2284
|
+
const settingsStore = useSettingsStore()
|
|
2285
|
+
// 没有对频道的支持计划
|
|
2286
|
+
if (data.detail_type == 'guild') {
|
|
2287
|
+
return
|
|
2288
|
+
}
|
|
2289
|
+
// 消息基础信息:优先使用 Satori 转换层生成的 infoList,避免 OneBot pathMap 误解析。
|
|
2290
|
+
const rawInfo = getDataObject(data.infoList ?? data)
|
|
2291
|
+
const id = getString(rawInfo.group_id) ||
|
|
2292
|
+
getString(rawInfo.private_id) ||
|
|
2293
|
+
getString(data.group_id) ||
|
|
2294
|
+
getString(data.user_id) ||
|
|
2295
|
+
getString(getDataObject(data.channel).id)
|
|
2296
|
+
const loginId = String(authStore.loginInfo.uin ?? '')
|
|
2297
|
+
const showId = String(chatStore.chatInfo.show.id ?? '')
|
|
2298
|
+
const sender = getString(rawInfo.sender) ||
|
|
2299
|
+
getString(getDataObject(data.sender).user_id) ||
|
|
2300
|
+
getString(data.user_id)
|
|
2301
|
+
const senderObj = getDataObject(data.sender)
|
|
2302
|
+
const targetId = getString(rawInfo.target_id) || getString(data.target_id) || loginId
|
|
2303
|
+
const rawChannelType = rawInfo.channel_type ?? data.channel_type
|
|
2304
|
+
const channelTypeNum = Number(rawChannelType)
|
|
2305
|
+
const hasDirectType = Number.isFinite(channelTypeNum)
|
|
2306
|
+
? channelTypeNum === 1
|
|
2307
|
+
: ['direct', 'private'].includes(String(rawChannelType ?? '').toLowerCase())
|
|
2308
|
+
const hasGroupType = Number.isFinite(channelTypeNum)
|
|
2309
|
+
? channelTypeNum === 0
|
|
2310
|
+
: ['text', 'group', 'room', 'chat', 'channel'].includes(String(rawChannelType ?? '').toLowerCase())
|
|
2311
|
+
const isGroupMessage = hasDirectType
|
|
2312
|
+
? false
|
|
2313
|
+
: hasGroupType
|
|
2314
|
+
? true
|
|
2315
|
+
: data.message_type === 'group' || Boolean(data.group_id) || Boolean(rawInfo.group_id)
|
|
2316
|
+
const sessionChannelId = getString(rawInfo.channel_id) || getString(data.channel_id)
|
|
2317
|
+
const sessionGuildId = getString(rawInfo.guild_id) || getString(data.guild_id)
|
|
2318
|
+
if (!id || id === '0') return
|
|
2319
|
+
// 在好友列表里找一下他
|
|
2320
|
+
const senderInfo = contactStore.userList.find((item) => {
|
|
2321
|
+
return String(item.user_id ?? '') === sender
|
|
2322
|
+
})
|
|
2323
|
+
const isImportant = senderInfo?.class_id == 9999
|
|
2324
|
+
|
|
2325
|
+
// 预发送消息填充 ============================================
|
|
2326
|
+
// 列表内最近的一条 fake_msg(倒序查找)
|
|
2327
|
+
let fakeMsg = null as any
|
|
2328
|
+
for (let i = chatStore.messageList.length - 1; i > 0; i--) {
|
|
2329
|
+
const msg = chatStore.messageList[i]
|
|
2330
|
+
if (msg.fake_msg != undefined && sender == loginId) {
|
|
2331
|
+
fakeMsg = msg
|
|
2332
|
+
break
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
// 预发送消息刷新
|
|
2336
|
+
if (fakeMsg != null) {
|
|
2337
|
+
// 将这条消息直接替换掉
|
|
2338
|
+
const trueMsg = getMsgData(
|
|
2339
|
+
'message_list',
|
|
2340
|
+
buildMsgList([data]),
|
|
2341
|
+
msgPath.message_list,
|
|
2342
|
+
)
|
|
2343
|
+
getMessageList(trueMsg).then((trueMsg) => {
|
|
2344
|
+
if (trueMsg?.length == 1) {
|
|
2345
|
+
// 使用消息对象引用直接更新,避免索引问题
|
|
2346
|
+
fakeMsg.message = trueMsg[0].message
|
|
2347
|
+
fakeMsg.raw_message = trueMsg[0].raw_message
|
|
2348
|
+
fakeMsg.time = trueMsg[0].time
|
|
2349
|
+
fakeMsg.fake_msg = undefined
|
|
2350
|
+
fakeMsg.revoke = false
|
|
2351
|
+
}
|
|
2352
|
+
})
|
|
2353
|
+
// 移除最顶端的一条消息以被动刷新整个列表
|
|
2354
|
+
chatStore.messageList.shift()
|
|
2355
|
+
return
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// 刷新 favicon
|
|
2359
|
+
refreshFavicon()
|
|
2360
|
+
|
|
2361
|
+
|
|
2362
|
+
|
|
2363
|
+
// 对消息进行一次格式化处理
|
|
2364
|
+
const list = normalizeNewIncomingMessage(data)
|
|
2365
|
+
|
|
2366
|
+
if (list.length > 0) {
|
|
2367
|
+
// 保存到本地历史
|
|
2368
|
+
saveMessagesWithSideEffects(authStore.loginInfo.uin, list)
|
|
2369
|
+
data = list[0]
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// 显示消息 ============================================
|
|
2373
|
+
if (id && (id === showId || targetId === showId)) {
|
|
2374
|
+
// 如果有正在输入的提示,清除它
|
|
2375
|
+
chatStore.chatInfo.show.appendInfo = undefined
|
|
2376
|
+
const cachedAvatar = getCachedUserAvatar(
|
|
2377
|
+
String(authStore.loginInfo.platform ?? ''),
|
|
2378
|
+
String(authStore.loginInfo.uin ?? ''),
|
|
2379
|
+
sender,
|
|
2380
|
+
)
|
|
2381
|
+
if (
|
|
2382
|
+
cachedAvatar &&
|
|
2383
|
+
(!senderObj.avatar || senderObj.avatar === '/img/icons/icon.svg')
|
|
2384
|
+
) {
|
|
2385
|
+
senderObj.avatar = cachedAvatar
|
|
2386
|
+
}
|
|
2387
|
+
// 保存消息:优先使用 Satori 转换后的消息结构,避免 pathMap 解析失败导致右侧不更新
|
|
2388
|
+
const incoming = Array.isArray(data.message)
|
|
2389
|
+
? data
|
|
2390
|
+
: normalizeNewIncomingMessage(data)[0] ?? data
|
|
2391
|
+
if (!incoming.infoList) {
|
|
2392
|
+
const groupId = getString(incoming.group_id)
|
|
2393
|
+
const userId = getString(incoming.user_id)
|
|
2394
|
+
incoming.infoList = {
|
|
2395
|
+
message_id: getString(incoming.message_id),
|
|
2396
|
+
private_id: groupId ? '' : userId,
|
|
2397
|
+
group_id: groupId || undefined,
|
|
2398
|
+
channel_id: getString(incoming.channel_id),
|
|
2399
|
+
guild_id: getString(incoming.guild_id),
|
|
2400
|
+
target_id: getString(incoming.target_id),
|
|
2401
|
+
sender: userId,
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
if (
|
|
2405
|
+
!chatStore.messageList.some((item) => {
|
|
2406
|
+
return String(item.message_id) === String(incoming.message_id)
|
|
2407
|
+
})
|
|
2408
|
+
) {
|
|
2409
|
+
chatStore.messageList.push(incoming)
|
|
2410
|
+
sendMsgAppendInfo(incoming)
|
|
2411
|
+
saveMessagesWithSideEffects(authStore.loginInfo.uin, [incoming])
|
|
2412
|
+
}
|
|
2413
|
+
// 抽个签
|
|
2414
|
+
const num = randomNum(0, 10000)
|
|
2415
|
+
if (num >= 400 && num <= 500) {
|
|
2416
|
+
logger.add(
|
|
2417
|
+
LogType.INFO,
|
|
2418
|
+
num.toString() + ',这只是个神秘的数字...',
|
|
2419
|
+
undefined,
|
|
2420
|
+
true,
|
|
2421
|
+
)
|
|
2422
|
+
}
|
|
2423
|
+
if (num === 495) { // QED怎么能和芙兰无关?(◣_◢)吃我一发 QED [495年的波纹]
|
|
2424
|
+
const popInfo = {
|
|
2425
|
+
html: qed,
|
|
2426
|
+
button: [
|
|
2427
|
+
{
|
|
2428
|
+
text: '确定(O)',
|
|
2429
|
+
fun: () => {
|
|
2430
|
+
uiStore.popBoxList.shift()
|
|
2431
|
+
},
|
|
2432
|
+
},
|
|
2433
|
+
],
|
|
2434
|
+
}
|
|
2435
|
+
uiStore.popBoxList.push(popInfo)
|
|
2436
|
+
}
|
|
2437
|
+
qed_try_times++
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// 通知判定预处理 ============================================
|
|
2441
|
+
// 对于其他不在消息里标记 atme、atall 的处理
|
|
2442
|
+
if (data.atme == undefined || data.atall == undefined) {
|
|
2443
|
+
if (!Array.isArray(data.message)) {
|
|
2444
|
+
data.message = []
|
|
2445
|
+
}
|
|
2446
|
+
data.message.forEach((item: any) => {
|
|
2447
|
+
if (item.type == 'at' && item.qq == loginId) {
|
|
2448
|
+
data.atme = true
|
|
2449
|
+
}
|
|
2450
|
+
})
|
|
2451
|
+
}
|
|
2452
|
+
// 临时会话名字的特殊处理
|
|
2453
|
+
if (data.sub_type === 'group' && senderObj.nickname !== undefined) {
|
|
2454
|
+
senderObj.nickname = senderObj.user_id
|
|
2455
|
+
}
|
|
2456
|
+
// 检查群组有没有开启通知
|
|
2457
|
+
let isGroupNotice = false
|
|
2458
|
+
if (isGroupMessage) {
|
|
2459
|
+
const noticeInfo = Option.get('notice_group') ?? {}
|
|
2460
|
+
const list = noticeInfo[authStore.loginInfo.uin]
|
|
2461
|
+
if (list) {
|
|
2462
|
+
isGroupNotice = list.indexOf(id) >= 0
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
const isTempGroupMessage = data.sub_type === 'group'
|
|
2467
|
+
const groupNoticeType = settingsStore.sysConfig.group_notice_type
|
|
2468
|
+
const hasForcedGroupInnerNotice = data.atme || data.atall || isImportant || isGroupNotice
|
|
2469
|
+
const allowGroupInnerNotice = !isGroupMessage ||
|
|
2470
|
+
groupNoticeType !== 'none' ||
|
|
2471
|
+
hasForcedGroupInnerNotice
|
|
2472
|
+
const allowGroupSystemNotice = !isGroupMessage ||
|
|
2473
|
+
groupNoticeType === 'all'
|
|
2474
|
+
|
|
2475
|
+
// 会话状态更新 ============================================
|
|
2476
|
+
const sessionId = isTempGroupMessage ? sender : id
|
|
2477
|
+
const normalizedSessionId = normalizeSessionId(sessionId)
|
|
2478
|
+
let session = contactStore.baseOnMsgList.get(normalizedSessionId)
|
|
2479
|
+
if (!session) {
|
|
2480
|
+
if (isTempGroupMessage) {
|
|
2481
|
+
session = {
|
|
2482
|
+
user_id: sender,
|
|
2483
|
+
nickname: app.config.globalProperties.$t('临时会话'),
|
|
2484
|
+
remark: senderObj.user_id,
|
|
2485
|
+
group_id: data.group_id,
|
|
2486
|
+
group_name: '',
|
|
2487
|
+
} as UserFriendElem & UserGroupElem
|
|
2488
|
+
} else {
|
|
2489
|
+
session = resolveIncomingSession(
|
|
2490
|
+
contactStore.userList,
|
|
2491
|
+
sessionId,
|
|
2492
|
+
isGroupMessage,
|
|
2493
|
+
getString(senderObj.nickname),
|
|
2494
|
+
)
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (session) {
|
|
2498
|
+
if (isGroupMessage && data.group_name) {
|
|
2499
|
+
if (!session.group_name || session.group_name === String(sessionId)) {
|
|
2500
|
+
session.group_name = String(data.group_name)
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
Object.assign(session, formatMessageData(data, isGroupMessage), {
|
|
2504
|
+
channel_id: sessionChannelId || undefined,
|
|
2505
|
+
guild_id: sessionGuildId || undefined,
|
|
2506
|
+
})
|
|
2507
|
+
if (
|
|
2508
|
+
sender != loginId &&
|
|
2509
|
+
sender !== '' &&
|
|
2510
|
+
sender !== '0' &&
|
|
2511
|
+
sessionId !== showId &&
|
|
2512
|
+
allowGroupInnerNotice
|
|
2513
|
+
) {
|
|
2514
|
+
if (!session.new_msg) {
|
|
2515
|
+
session.new_msg = true
|
|
2516
|
+
contactStore.newMsgCount++
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
if (sessionId !== showId && allowGroupInnerNotice) {
|
|
2520
|
+
if (data.atme) { session.highlight = $t('[有人@你]') }
|
|
2521
|
+
if (data.atall) { session.highlight = $t('[@全体]') }
|
|
2522
|
+
if (isImportant) { session.highlight = $t('[特別关心]') }
|
|
2523
|
+
}
|
|
2524
|
+
contactStore.baseOnMsgList.set(normalizedSessionId, session)
|
|
2525
|
+
updateBaseOnMsgList()
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// 通知判定 ============================================
|
|
2529
|
+
if (
|
|
2530
|
+
sender != loginId &&
|
|
2531
|
+
sender !== '' &&
|
|
2532
|
+
sender !== '0' &&
|
|
2533
|
+
allowGroupSystemNotice
|
|
2534
|
+
) {
|
|
2535
|
+
logger.add(LogType.DEBUG, '通知判定:', {
|
|
2536
|
+
notShow: sessionId !== showId,
|
|
2537
|
+
notFocus: !document.hasFocus(),
|
|
2538
|
+
hidden: document.hidden,
|
|
2539
|
+
isImportant: isImportant
|
|
2540
|
+
})
|
|
2541
|
+
// (发送者没有被打开 || 窗口没有焦点 || 窗口被最小化 || 在特别关心列表里) 这些情况需要进行消息通知
|
|
2542
|
+
const forceGroupSystemNotice = isGroupMessage && groupNoticeType === 'all'
|
|
2543
|
+
const forceImportantNotice = isImportant ||
|
|
2544
|
+
(isGroupMessage && (data.atme || data.atall || isGroupNotice))
|
|
2545
|
+
if (
|
|
2546
|
+
forceGroupSystemNotice ||
|
|
2547
|
+
forceImportantNotice ||
|
|
2548
|
+
sessionId !== showId ||
|
|
2549
|
+
!document.hasFocus() ||
|
|
2550
|
+
document.hidden
|
|
2551
|
+
) {
|
|
2552
|
+
// 准备消息内容
|
|
2553
|
+
let raw = getMsgRawTxt(data)
|
|
2554
|
+
raw = raw === '' ? data.raw_message : raw
|
|
2555
|
+
logger.add(LogType.INFO, '新消息通知:' + raw, undefined, true)
|
|
2556
|
+
if (data.group_name === undefined) {
|
|
2557
|
+
// 检查消息内是否有群名,去列表里寻找
|
|
2558
|
+
contactStore.userList.forEach((item) => {
|
|
2559
|
+
if (item.group_id == data.group_id) {
|
|
2560
|
+
data.group_name = item.group_name
|
|
2561
|
+
}
|
|
2562
|
+
})
|
|
2563
|
+
}
|
|
2564
|
+
const msgInfo = {
|
|
2565
|
+
base_type: 'msg',
|
|
2566
|
+
|
|
2567
|
+
title: data.group_name ?? senderObj.nickname,
|
|
2568
|
+
body:
|
|
2569
|
+
isGroupMessage ? String(senderObj.nickname ?? '') + ':' + raw : raw,
|
|
2570
|
+
tag: `${sessionId}/${data.message_id}`,
|
|
2571
|
+
icon: '/img/icons/icon.svg',
|
|
2572
|
+
image: undefined as any,
|
|
2573
|
+
type: isGroupMessage ? 'group' : 'user',
|
|
2574
|
+
is_important: isImportant,
|
|
2575
|
+
} as NotifyInfo
|
|
2576
|
+
if (Array.isArray(data.message)) {
|
|
2577
|
+
data.message.forEach((item: MsgItemElem) => {
|
|
2578
|
+
// 如果消息有图片,追加第一张图片
|
|
2579
|
+
if (item.type === 'image' && msgInfo.image === undefined) {
|
|
2580
|
+
msgInfo.image = item.url
|
|
2581
|
+
}
|
|
2582
|
+
})
|
|
2583
|
+
}
|
|
2584
|
+
const isGroupImportantNotice = isGroupMessage &&
|
|
2585
|
+
(data.atme || data.atall || isImportant || isGroupNotice)
|
|
2586
|
+
if (isGroupImportantNotice && groupNoticeType !== 'none') {
|
|
2587
|
+
new PopInfo().add(PopType.INFO, `${msgInfo.title}: ${msgInfo.body}`)
|
|
2588
|
+
}
|
|
2589
|
+
// 发送消息
|
|
2590
|
+
new Notify().notify(msgInfo)
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
/**
|
|
2596
|
+
* 刷新系统通知和其他内容,给系统通知响应用的
|
|
2597
|
+
*/
|
|
2598
|
+
function updateSysInfo(
|
|
2599
|
+
_: string,
|
|
2600
|
+
__: { [key: string]: any },
|
|
2601
|
+
echoList: string[],
|
|
2602
|
+
) {
|
|
2603
|
+
const contactStore = useContactStore()
|
|
2604
|
+
const flag = echoList[1]
|
|
2605
|
+
// 从系统通知列表里删除这条消息
|
|
2606
|
+
if (flag !== undefined) {
|
|
2607
|
+
const index = contactStore.systemNoticesList?.findIndex((item: any) => {
|
|
2608
|
+
return item.flag == flag
|
|
2609
|
+
})
|
|
2610
|
+
if (index !== -1) {
|
|
2611
|
+
contactStore.systemNoticesList?.splice(index, 1)
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// ==============================================================
|
|
2617
|
+
|
|
2618
|
+
function formatMessageData(data: any, isGroup: boolean) {
|
|
2619
|
+
const name = data.sender?.card && data.sender.card !== '' ? data.sender.card : data.sender?.nickname
|
|
2620
|
+
const raw = getMsgRawTxt(data) || data.raw_message || ''
|
|
2621
|
+
const rawTime = Number(data.time)
|
|
2622
|
+
const time = Number.isFinite(rawTime) && rawTime > 0 ? getViewTime(rawTime) : Date.now()
|
|
2623
|
+
|
|
2624
|
+
return {
|
|
2625
|
+
message_id: data.message_id,
|
|
2626
|
+
raw_msg: isGroup && name ? `${name}: ${raw}` : raw,
|
|
2627
|
+
highlight: hasAtMe(data) ? '[有人@你]' : undefined,
|
|
2628
|
+
time,
|
|
2629
|
+
raw_msg_base: raw,
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
// 重置 Runtime,但是保留应用设置之类已经加载好的应用内容
|
|
2634
|
+
export function resetRimtime(resetAll = false) {
|
|
2635
|
+
firstHeartbeatTime = -1
|
|
2636
|
+
heartbeatTime = -1
|
|
2637
|
+
clearMetaEventWatchdog()
|
|
2638
|
+
groupPreviewHydrator.reset()
|
|
2639
|
+
if (resetAll) {
|
|
2640
|
+
// Reset auth store
|
|
2641
|
+
const authStore = useAuthStore()
|
|
2642
|
+
authStore.loginInfo = reactive({})
|
|
2643
|
+
authStore.botInfo = reactive({})
|
|
2644
|
+
// Reset contact store
|
|
2645
|
+
const contactStore = useContactStore()
|
|
2646
|
+
contactStore.userList = reactive([])
|
|
2647
|
+
contactStore.showList = reactive([])
|
|
2648
|
+
contactStore.systemNoticesList = reactive([])
|
|
2649
|
+
contactStore.baseOnMsgList = reactive(new Map())
|
|
2650
|
+
contactStore.onMsgList = reactive([])
|
|
2651
|
+
contactStore.groupAssistList = reactive([])
|
|
2652
|
+
// Reset chat store
|
|
2653
|
+
const chatStore = useChatStore()
|
|
2654
|
+
chatStore.chatInfo = reactive({
|
|
2655
|
+
show: { type: '', id: 0, name: '', avatar: '' },
|
|
2656
|
+
info: {
|
|
2657
|
+
group_info: {},
|
|
2658
|
+
user_info: {},
|
|
2659
|
+
me_info: {},
|
|
2660
|
+
group_members: [],
|
|
2661
|
+
group_files: {},
|
|
2662
|
+
group_sub_files: {},
|
|
2663
|
+
jin_info: { list: [] as { [key: string]: any }[], pages: 0 },
|
|
2664
|
+
},
|
|
2665
|
+
})
|
|
2666
|
+
chatStore.messageList = []
|
|
2667
|
+
// Reset connection store
|
|
2668
|
+
const connectionStore = useConnectionStore()
|
|
2669
|
+
connectionStore.heartbeatTime = -1
|
|
2670
|
+
connectionStore.oldHeartbeatTime = -1
|
|
2671
|
+
connectionStore.lastHeartbeatTime = -1
|
|
2672
|
+
connectionStore.backTimes = 0
|
|
2673
|
+
}
|
|
2674
|
+
}
|