koishi-plugin-chat-patch 5.1.0 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/web/dist/assets/Chat-Bo5JdcNg.js +28 -0
- package/client/web/dist/assets/{Chat-CTdyeaX2.css → Chat-C-rmzMIw.css} +1 -1
- package/client/web/dist/assets/{MsgBody-DLeOklIb.js → MsgBody-h4C1nnBQ.js} +1 -1
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-a902ps2r.js +67 -0
- package/client/web/dist/assets/{index-Cz89G4we.js → index-CdTmWcO4.js} +121 -129
- package/client/web/dist/assets/index-wjp_A4n1.css +1 -0
- package/client/web/dist/assets/pinyin-Dsq98249.js +157657 -0
- package/client/web/dist/index.html +2 -2
- package/client/web/dist/notice_local.json +1 -0
- package/client/web/package-lock.json +49 -0
- package/client/web/package.json +1 -0
- package/client/web/public/notice_local.json +1 -0
- package/client/web/src/components/AboutPan.vue +2 -1
- package/client/web/src/components/BulletinBody.vue +1 -1
- package/client/web/src/components/EmojiFace.vue +0 -6
- package/client/web/src/components/FacePan.vue +0 -12
- package/client/web/src/components/FriendBody.vue +1 -0
- package/client/web/src/components/MsgBody.vue +4 -4
- package/client/web/src/components/SettingsTab.vue +133 -0
- package/client/web/src/components/TinySessionBody.vue +1 -0
- package/client/web/src/components/UpdatePan.vue +2 -2
- package/client/web/src/components/tooltip/UserInfoTooltip.vue +1 -1
- package/client/web/src/function/connect.ts +81 -28
- package/client/web/src/function/model/emoji.ts +7 -67
- package/client/web/src/function/msg.ts +68 -26
- package/client/web/src/function/satori-model.ts +8 -0
- package/client/web/src/function/satori.ts +31 -1
- package/client/web/src/function/utils/appUtil.ts +1 -4
- package/client/web/src/function/utils/avatarUtil.ts +18 -3
- package/client/web/src/function/utils/msgUtil.ts +86 -52
- package/client/web/src/function/utils/pinyin.ts +25 -65
- package/client/web/src/function/utils/sessionUtil.ts +90 -14
- package/client/web/src/main.ts +5 -0
- package/client/web/src/pages/Chat.vue +3 -4
- package/client/web/src/pages/Friends.vue +72 -30
- package/client/web/src/pages/Info.vue +3 -3
- package/client/web/src/pages/Messages.vue +59 -26
- package/client/web/src/pages/Options.vue +4 -4
- package/client/web/src/pages/Qzone.vue +2 -2
- package/client/web/src/pages/chat-view/SystemNotice.vue +3 -3
- package/client/web/src/pages/options/OptAccount.vue +6 -4
- package/client/web/src/pages/options/OptDev.vue +1 -14
- package/lib/index.js +14 -14
- package/lib/types.d.ts +115 -0
- package/package.json +1 -1
- package/src/bootstrap.ts +1 -0
- package/src/config.ts +1 -1
- package/src/recorder.ts +7 -7
- package/src/types.ts +4 -0
- package/src/web.ts +8 -9
- package/client/web/dist/assets/Chat-BoNiWGxb.js +0 -28
- package/client/web/dist/assets/MsgBody.vue_vue_type_script_setup_true_lang-CGjy4Jzz.js +0 -67
- package/client/web/dist/assets/index-BgOX_tNa.css +0 -1
|
@@ -3,30 +3,7 @@ export default class Emoji {
|
|
|
3
3
|
static readonly descMap = new Map<string, string>()
|
|
4
4
|
static readonly allList = new Set<number>()
|
|
5
5
|
static readonly allSuperList = new Set<number>()
|
|
6
|
-
|
|
7
|
-
static readonly superList: readonly number[] = [
|
|
8
|
-
5, 311, 312, 314, 317, 318, 319, 320, 324, 325, 337,
|
|
9
|
-
338, 339, 341, 342, 343, 344, 345, 346, 181, 74, 75,
|
|
10
|
-
351, 349, 350, 395, 114, 326, 53, 137, 333, 424, 415,
|
|
11
|
-
392, 425, 427, 426, 419, 429, 472, 474, 475, 476, 477,
|
|
12
|
-
478, 479,
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
static readonly normalList: readonly number[] = [
|
|
16
|
-
14, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 0, 15, 16,
|
|
17
|
-
96, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
|
18
|
-
31, 32, 33, 34, 35, 36, 37, 38, 39, 97, 98, 99, 100,
|
|
19
|
-
101, 102, 103, 104, 105, 106, 107, 108, 305, 109, 110,
|
|
20
|
-
111, 172, 182, 179, 173, 174, 212, 175, 178, 177, 176,
|
|
21
|
-
183, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271,
|
|
22
|
-
272, 277, 307, 306, 281, 282, 283, 284, 285, 293, 286,
|
|
23
|
-
287, 289, 294, 297, 298, 299, 300, 323, 332, 336, 353,
|
|
24
|
-
355, 356, 354, 352, 357, 428, 334, 347, 303, 302, 295,
|
|
25
|
-
49, 66, 63, 64, 187, 146, 116, 67, 60, 185, 76, 124,
|
|
26
|
-
118, 78, 119, 79, 120, 121, 77, 123, 201, 273, 46, 112,
|
|
27
|
-
56, 169, 171, 59, 144, 147, 148, 89, 41, 125, 42, 43, 86, 129,
|
|
28
|
-
85,
|
|
29
|
-
]
|
|
6
|
+
static readonly normalList: readonly number[] = []
|
|
30
7
|
|
|
31
8
|
static readonly emojiList: readonly number[] = [
|
|
32
9
|
128522, 128524, 128538, 128531, 128560, 128541, 128513, 128540,
|
|
@@ -40,19 +17,6 @@ export default class Emoji {
|
|
|
40
17
|
10068, 128299, 128147, 127978,
|
|
41
18
|
]
|
|
42
19
|
|
|
43
|
-
static readonly responseApngId: readonly number[] = [
|
|
44
|
-
5, 314, 318, 319, 320, 324, 337, 338, 339, 341, 342, 343, 344,
|
|
45
|
-
345, 346, 181, 74, 75, 351, 349, 350, 395, 326, 53, 333, 424,
|
|
46
|
-
425, 427, 426, 14, 4, 8, 9, 10, 12, 16, 96, 21, 23, 24, 25, 26,
|
|
47
|
-
27, 28, 29, 30, 32, 33, 34, 38, 39, 97, 98, 99, 100, 101, 102,
|
|
48
|
-
103, 104, 106, 305, 109, 111, 182, 179, 173, 174, 212, 175, 176,
|
|
49
|
-
183, 262, 264, 265, 266, 267, 268, 269, 270, 271, 272, 277, 307,
|
|
50
|
-
306, 281, 282, 284, 285, 293, 287, 289, 294, 297, 298, 299, 332,
|
|
51
|
-
336, 353, 355, 356, 354, 352, 357, 428, 334, 347, 303, 302, 295,
|
|
52
|
-
49, 66, 63, 116, 60, 76, 124, 118, 78, 79, 120, 123, 201, 273,
|
|
53
|
-
171, 144, 147, 89, 41, 125, 42, 43, 129, 85,
|
|
54
|
-
]
|
|
55
|
-
|
|
56
20
|
static readonly responseEmojiId: readonly number[] = [
|
|
57
21
|
128522, 128524, 128538, 128531, 128560, 128541, 128513, 128540,
|
|
58
22
|
9786, 128532, 128516, 128527, 128530, 128563, 128536, 128557,
|
|
@@ -67,7 +31,7 @@ export default class Emoji {
|
|
|
67
31
|
|
|
68
32
|
private constructor(
|
|
69
33
|
public id: number,
|
|
70
|
-
public type: '
|
|
34
|
+
public type: 'emoji',
|
|
71
35
|
public hasSuper: boolean,
|
|
72
36
|
public superSuffix: number[],
|
|
73
37
|
) {
|
|
@@ -77,28 +41,16 @@ export default class Emoji {
|
|
|
77
41
|
}
|
|
78
42
|
|
|
79
43
|
static get(id: number): Emoji | undefined {
|
|
80
|
-
if (id < 5000)
|
|
81
|
-
const value = this.apngMap.get(id)
|
|
82
|
-
return value ? new Emoji(id, 'apng', value.super, value.suffix) : undefined
|
|
83
|
-
}
|
|
44
|
+
if (id < 5000) return undefined
|
|
84
45
|
return new Emoji(id, 'emoji', false, [])
|
|
85
46
|
}
|
|
86
47
|
|
|
87
48
|
static has(id: number): boolean {
|
|
88
|
-
return id >= 5000
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
static init() {
|
|
92
|
-
for (const id of [...this.superList, ...this.normalList, ...this.responseApngId]) {
|
|
93
|
-
const isSuper = this.superList.includes(id)
|
|
94
|
-
this.apngMap.set(id, { super: isSuper, suffix: [] })
|
|
95
|
-
if (isSuper) this.allSuperList.add(id)
|
|
96
|
-
this.allList.add(id)
|
|
97
|
-
}
|
|
49
|
+
return id >= 5000
|
|
98
50
|
}
|
|
99
51
|
|
|
100
52
|
static get responseId(): readonly number[] {
|
|
101
|
-
return
|
|
53
|
+
return this.responseEmojiId
|
|
102
54
|
}
|
|
103
55
|
|
|
104
56
|
get description(): string {
|
|
@@ -106,22 +58,10 @@ export default class Emoji {
|
|
|
106
58
|
}
|
|
107
59
|
|
|
108
60
|
get value(): string {
|
|
109
|
-
return
|
|
61
|
+
return String.fromCodePoint(this.id)
|
|
110
62
|
}
|
|
111
63
|
|
|
112
64
|
get superValue(): string | undefined {
|
|
113
|
-
|
|
114
|
-
return this.getSuperUrl(this.id, this.suffixId)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private getNormalUrl(id: number): string {
|
|
118
|
-
return `https://lib.stapxs.cn/download/stapxs-qq-lite/qq_emoji/${id}/apng/${id}.png`
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private getSuperUrl(id: number, suffix?: number): string {
|
|
122
|
-
const name = suffix ? `${id}_${suffix}` : `${id}`
|
|
123
|
-
return `https://lib.stapxs.cn/download/stapxs-qq-lite/qq_emoji/${id}/lottie/${name}.json`
|
|
65
|
+
return undefined
|
|
124
66
|
}
|
|
125
67
|
}
|
|
126
|
-
|
|
127
|
-
setTimeout(Emoji.init.bind(Emoji), 0)
|
|
@@ -87,6 +87,7 @@ import { useUIStore } from '../state/ui'
|
|
|
87
87
|
import { useSettingsStore } from '../state/settings'
|
|
88
88
|
import { useQzoneStore } from '../state/qzone'
|
|
89
89
|
import {
|
|
90
|
+
findSessionContact,
|
|
90
91
|
getSessionId,
|
|
91
92
|
getMissingGroupPreviewSessions,
|
|
92
93
|
mergeEarlySessionContacts,
|
|
@@ -119,6 +120,11 @@ function getString(value: unknown): string {
|
|
|
119
120
|
return typeof value === 'string' ? value : String(value)
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
function normalizeChannelForMatch(value: string): string {
|
|
124
|
+
if (/^(?:private|direct):/i.test(value)) return value.replace(/^(?:private|direct):/i, '')
|
|
125
|
+
return normalizeGroupId(value)
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
// 其他 tag
|
|
123
129
|
let listLoadTimes = 0
|
|
124
130
|
const logger = new Logger()
|
|
@@ -963,9 +969,11 @@ const msgFunctions = {
|
|
|
963
969
|
)
|
|
964
970
|
// 更新消息列表
|
|
965
971
|
const onmsg = contactStore.baseOnMsgList.get(normalizeSessionId(id))
|
|
972
|
+
?? findSessionContact(Array.from(contactStore.baseOnMsgList.values()), id)
|
|
966
973
|
if (onmsg && list[0]) {
|
|
974
|
+
const canonicalId = normalizeSessionId(getSessionId(onmsg))
|
|
967
975
|
Object.assign(onmsg, formatMessageData(list[0], Boolean(onmsg.group_id)))
|
|
968
|
-
contactStore.baseOnMsgList.set(
|
|
976
|
+
contactStore.baseOnMsgList.set(canonicalId, onmsg)
|
|
969
977
|
updateBaseOnMsgList()
|
|
970
978
|
}
|
|
971
979
|
}
|
|
@@ -1606,12 +1614,18 @@ function saveUser(msg: { [key: string]: any }, type: string) {
|
|
|
1606
1614
|
list = list.filter((item, index, arr) => {
|
|
1607
1615
|
return (
|
|
1608
1616
|
arr.findIndex((item2) => {
|
|
1609
|
-
const
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1617
|
+
const channelA = String(item2.channel_id ?? item2.channelId ?? '')
|
|
1618
|
+
const channelB = String(item.channel_id ?? item.channelId ?? '')
|
|
1619
|
+
const groupA = String(item2.group_id ?? item2.id ?? item2.guild_id ?? '')
|
|
1620
|
+
const groupB = String(item.group_id ?? item.id ?? item.guild_id ?? '')
|
|
1621
|
+
if (channelA && channelB) {
|
|
1622
|
+
return normalizeGroupId(channelA) === normalizeGroupId(channelB)
|
|
1623
|
+
}
|
|
1624
|
+
if (channelA || channelB) {
|
|
1625
|
+
return normalizeGroupId(channelA || channelB)
|
|
1626
|
+
=== normalizeGroupId(groupA || groupB)
|
|
1627
|
+
}
|
|
1628
|
+
return normalizeGroupId(groupA) === normalizeGroupId(groupB)
|
|
1615
1629
|
}) == index
|
|
1616
1630
|
)
|
|
1617
1631
|
})
|
|
@@ -1681,15 +1695,34 @@ function saveUser(msg: { [key: string]: any }, type: string) {
|
|
|
1681
1695
|
list,
|
|
1682
1696
|
contactStore.baseOnMsgList,
|
|
1683
1697
|
)
|
|
1684
|
-
const
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1698
|
+
const sessionDedupKey = (item: any): string => {
|
|
1699
|
+
const channel = String(item.channel_id ?? item.channelId ?? '')
|
|
1700
|
+
const group = String(item.group_id ?? '')
|
|
1701
|
+
const user = String(item.user_id ?? '')
|
|
1702
|
+
if (group) return `group:${normalizeGroupId(channel || group)}`
|
|
1703
|
+
return `user:${String(user).replace(/^(?:private|direct):/i, '')}`
|
|
1704
|
+
}
|
|
1705
|
+
const existingIds = new Set(contactStore.userList.map(sessionDedupKey))
|
|
1689
1706
|
const freshList = list.filter((item) => {
|
|
1690
|
-
return !existingIds.has(
|
|
1707
|
+
return !existingIds.has(sessionDedupKey(item))
|
|
1691
1708
|
})
|
|
1692
|
-
|
|
1709
|
+
const mergedContacts = [...contactStore.userList, ...freshList] as (UserFriendElem & UserGroupElem)[]
|
|
1710
|
+
const userMap = new Map<string, UserFriendElem & UserGroupElem>()
|
|
1711
|
+
for (const item of mergedContacts) {
|
|
1712
|
+
const key = sessionDedupKey(item)
|
|
1713
|
+
const existing = userMap.get(key)
|
|
1714
|
+
if (existing) {
|
|
1715
|
+
const pyName = existing.py_name
|
|
1716
|
+
const pyStart = existing.py_start
|
|
1717
|
+
Object.assign(existing, item)
|
|
1718
|
+
if (pyName) existing.py_name = pyName
|
|
1719
|
+
if (pyStart) existing.py_start = pyStart
|
|
1720
|
+
userMap.set(key, existing)
|
|
1721
|
+
} else {
|
|
1722
|
+
userMap.set(key, item)
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
contactStore.userList = [...userMap.values()]
|
|
1693
1726
|
if (
|
|
1694
1727
|
settingsStore.sysConfig.session_display_mode === 'all' ||
|
|
1695
1728
|
didMergeEarlySessions
|
|
@@ -1858,13 +1891,11 @@ async function saveMsg(msg: any, append = undefined as undefined | string) {
|
|
|
1858
1891
|
const lastMsg =
|
|
1859
1892
|
chatStore.messageList[chatStore.messageList.length - 1]
|
|
1860
1893
|
if (lastMsg) {
|
|
1861
|
-
const user = contactStore.userList.
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
})
|
|
1867
|
-
const sessionId = normalizeSessionId(chatStore.chatInfo.show.id)
|
|
1894
|
+
const user = findSessionContact(contactStore.userList, chatStore.chatInfo.show.id)
|
|
1895
|
+
const sessionId = normalizeSessionId(
|
|
1896
|
+
chatStore.chatInfo.show.channel_id
|
|
1897
|
+
?? chatStore.chatInfo.show.id,
|
|
1898
|
+
)
|
|
1868
1899
|
const session = contactStore.baseOnMsgList.get(sessionId) ?? user
|
|
1869
1900
|
if (session) {
|
|
1870
1901
|
const preview = formatMessageData(
|
|
@@ -2302,6 +2333,7 @@ function newMsg(_: string, data: any) {
|
|
|
2302
2333
|
getString(getDataObject(data.channel).id)
|
|
2303
2334
|
const loginId = String(authStore.loginInfo.uin ?? '')
|
|
2304
2335
|
const showId = String(chatStore.chatInfo.show.id ?? '')
|
|
2336
|
+
const showChannelId = getString(chatStore.chatInfo.show.channel_id)
|
|
2305
2337
|
const sender = getString(rawInfo.sender) ||
|
|
2306
2338
|
getString(getDataObject(data.sender).user_id) ||
|
|
2307
2339
|
getString(data.user_id)
|
|
@@ -2323,6 +2355,16 @@ function newMsg(_: string, data: any) {
|
|
|
2323
2355
|
const sessionChannelId = getString(rawInfo.channel_id) || getString(data.channel_id)
|
|
2324
2356
|
const sessionGuildId = getString(rawInfo.guild_id) || getString(data.guild_id)
|
|
2325
2357
|
if (!id || id === '0') return
|
|
2358
|
+
const isCurrentSession = Boolean(
|
|
2359
|
+
(
|
|
2360
|
+
sessionChannelId &&
|
|
2361
|
+
showChannelId &&
|
|
2362
|
+
normalizeChannelForMatch(sessionChannelId) === normalizeChannelForMatch(showChannelId)
|
|
2363
|
+
) || (
|
|
2364
|
+
(!sessionChannelId || !showChannelId) &&
|
|
2365
|
+
(id === showId || targetId === showId || String(sessionChannelId || sessionGuildId || '') === showId)
|
|
2366
|
+
),
|
|
2367
|
+
)
|
|
2326
2368
|
// 在好友列表里找一下他
|
|
2327
2369
|
const senderInfo = contactStore.userList.find((item) => {
|
|
2328
2370
|
return String(item.user_id ?? '') === sender
|
|
@@ -2377,7 +2419,7 @@ function newMsg(_: string, data: any) {
|
|
|
2377
2419
|
}
|
|
2378
2420
|
|
|
2379
2421
|
// 显示消息 ============================================
|
|
2380
|
-
if (id &&
|
|
2422
|
+
if (id && isCurrentSession) {
|
|
2381
2423
|
// 如果有正在输入的提示,清除它
|
|
2382
2424
|
chatStore.chatInfo.show.appendInfo = undefined
|
|
2383
2425
|
const cachedAvatar = getCachedUserAvatar(
|
|
@@ -2517,7 +2559,7 @@ function newMsg(_: string, data: any) {
|
|
|
2517
2559
|
sender != loginId &&
|
|
2518
2560
|
sender !== '' &&
|
|
2519
2561
|
sender !== '0' &&
|
|
2520
|
-
|
|
2562
|
+
!isCurrentSession &&
|
|
2521
2563
|
allowGroupInnerNotice
|
|
2522
2564
|
) {
|
|
2523
2565
|
if (!session.new_msg) {
|
|
@@ -2525,7 +2567,7 @@ function newMsg(_: string, data: any) {
|
|
|
2525
2567
|
contactStore.newMsgCount++
|
|
2526
2568
|
}
|
|
2527
2569
|
}
|
|
2528
|
-
if (
|
|
2570
|
+
if (!isCurrentSession && allowGroupInnerNotice) {
|
|
2529
2571
|
if (data.atme) { session.highlight = $t('[有人@你]') }
|
|
2530
2572
|
if (data.atall) { session.highlight = $t('[@全体]') }
|
|
2531
2573
|
if (isImportant) { session.highlight = $t('[特別关心]') }
|
|
@@ -2542,7 +2584,7 @@ function newMsg(_: string, data: any) {
|
|
|
2542
2584
|
allowGroupSystemNotice
|
|
2543
2585
|
) {
|
|
2544
2586
|
logger.add(LogType.DEBUG, '通知判定:', {
|
|
2545
|
-
notShow:
|
|
2587
|
+
notShow: !isCurrentSession,
|
|
2546
2588
|
notFocus: !document.hasFocus(),
|
|
2547
2589
|
hidden: document.hidden,
|
|
2548
2590
|
isImportant: isImportant
|
|
@@ -2554,7 +2596,7 @@ function newMsg(_: string, data: any) {
|
|
|
2554
2596
|
if (
|
|
2555
2597
|
forceGroupSystemNotice ||
|
|
2556
2598
|
forceImportantNotice ||
|
|
2557
|
-
|
|
2599
|
+
!isCurrentSession ||
|
|
2558
2600
|
!document.hasFocus() ||
|
|
2559
2601
|
document.hidden
|
|
2560
2602
|
) {
|
|
@@ -234,6 +234,13 @@ function containsElementMarkup(source: string): boolean {
|
|
|
234
234
|
return /<(img|image|audio|video|file|mface|face|quote|at|forward|json|xml|markdown|keyboard|p|br|i18n|a|text|sharp)(?:\s|\/|>)/i.test(source)
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// QQ 官方机器人会把部分大表情/图片消息的私有标记塞进 content,渲染前需要去掉
|
|
238
|
+
function cleanQqFaceMarkup(source: string): string {
|
|
239
|
+
return source
|
|
240
|
+
.replace(/<faceType\b[^>]*>/gi, '')
|
|
241
|
+
.replace(/<faceType\b[^>]*>/gi, '')
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
function splitMarkupTextSegments(
|
|
238
245
|
segments: Array<Record<string, unknown>>,
|
|
239
246
|
): Array<Record<string, unknown>> {
|
|
@@ -250,6 +257,7 @@ function splitMarkupTextSegments(
|
|
|
250
257
|
}
|
|
251
258
|
|
|
252
259
|
export function parseSatoriMarkup(source: string): Array<Record<string, unknown>> {
|
|
260
|
+
source = cleanQqFaceMarkup(source)
|
|
253
261
|
const result: Array<Record<string, unknown>> = []
|
|
254
262
|
let index = 0
|
|
255
263
|
while (index < source.length) {
|
|
@@ -4,6 +4,10 @@ export interface SatoriBootstrap {
|
|
|
4
4
|
endpoint: string
|
|
5
5
|
token: string
|
|
6
6
|
basePath: string
|
|
7
|
+
blockedPlatforms: Array<{
|
|
8
|
+
platformName: string
|
|
9
|
+
exactMatch: boolean
|
|
10
|
+
}>
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export interface SatoriLogin {
|
|
@@ -24,6 +28,11 @@ export interface SatoriEvent {
|
|
|
24
28
|
body: Record<string, unknown>
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
// 同一机器人 ID 可能同时存在于不同平台(例如 qq / qqguild),用平台 + ID 做唯一键
|
|
32
|
+
export function botKey(platform: string, selfId: string): string {
|
|
33
|
+
return [platform, selfId].map((value) => encodeURIComponent(value)).join(':')
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
interface PendingConsoleRequest {
|
|
28
37
|
resolve: (value: unknown) => void
|
|
29
38
|
reject: (reason: unknown) => void
|
|
@@ -47,6 +56,17 @@ function getObject(value: unknown): Record<string, unknown> {
|
|
|
47
56
|
return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {}
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
function isBlockedPlatform(
|
|
60
|
+
platform: string,
|
|
61
|
+
blockedPlatforms: SatoriBootstrap['blockedPlatforms'],
|
|
62
|
+
): boolean {
|
|
63
|
+
return (blockedPlatforms ?? []).some((item) => {
|
|
64
|
+
return item.exactMatch
|
|
65
|
+
? platform === item.platformName
|
|
66
|
+
: platform.includes(item.platformName)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
async function parseJsonResponse(response: Response): Promise<unknown> {
|
|
51
71
|
const text = await response.text()
|
|
52
72
|
if (!response.ok) {
|
|
@@ -94,6 +114,9 @@ function receiveBootstrap(data: Record<string, unknown>) {
|
|
|
94
114
|
endpoint: getString(payload.endpoint) || '/satori',
|
|
95
115
|
token: getString(payload.token),
|
|
96
116
|
basePath: getString(payload.basePath) || '/chat-patch',
|
|
117
|
+
blockedPlatforms: Array.isArray(payload.blockedPlatforms)
|
|
118
|
+
? payload.blockedPlatforms as SatoriBootstrap['blockedPlatforms']
|
|
119
|
+
: [],
|
|
97
120
|
}
|
|
98
121
|
window.clearTimeout(bootstrapTimeout)
|
|
99
122
|
resolveBootstrapCallback?.(bootstrap)
|
|
@@ -118,6 +141,10 @@ export function getBootstrap(): Promise<SatoriBootstrap> {
|
|
|
118
141
|
return resolveBootstrap()
|
|
119
142
|
}
|
|
120
143
|
|
|
144
|
+
export function getBlockedPlatforms(): SatoriBootstrap['blockedPlatforms'] {
|
|
145
|
+
return bootstrap?.blockedPlatforms ?? []
|
|
146
|
+
}
|
|
147
|
+
|
|
121
148
|
export function getLogins(): SatoriLogin[] {
|
|
122
149
|
return logins
|
|
123
150
|
}
|
|
@@ -219,6 +246,9 @@ export function connect(
|
|
|
219
246
|
endpoint: getString(payload.endpoint) || '/satori',
|
|
220
247
|
token: getString(payload.token),
|
|
221
248
|
basePath: getString(payload.basePath) || '/chat-patch',
|
|
249
|
+
blockedPlatforms: Array.isArray(payload.blockedPlatforms)
|
|
250
|
+
? payload.blockedPlatforms as SatoriBootstrap['blockedPlatforms']
|
|
251
|
+
: [],
|
|
222
252
|
}
|
|
223
253
|
}
|
|
224
254
|
}
|
|
@@ -258,7 +288,7 @@ export function connect(
|
|
|
258
288
|
const platform = getString(login.platform) || getString(user.platform)
|
|
259
289
|
const selfId = getString(user.id) || getString(login.selfId)
|
|
260
290
|
const key = [platform, selfId].map((value) => encodeURIComponent(value)).join(':')
|
|
261
|
-
if (!platform || !selfId || seen.has(key)) continue
|
|
291
|
+
if (!platform || !selfId || seen.has(key) || isBlockedPlatform(platform, bootstrap?.blockedPlatforms ?? [])) continue
|
|
262
292
|
seen.add(key)
|
|
263
293
|
nextLogins.push({
|
|
264
294
|
platform: getString(login.platform) || getString(user.platform),
|
|
@@ -1026,10 +1026,7 @@ export function checkOpenTimes() {
|
|
|
1026
1026
|
*/
|
|
1027
1027
|
export function checkNotice() {
|
|
1028
1028
|
const uiStore = useUIStore()
|
|
1029
|
-
|
|
1030
|
-
if (import.meta.env.DEV) {
|
|
1031
|
-
url = 'notice_local.json'
|
|
1032
|
-
}
|
|
1029
|
+
const url = 'notice_local.json'
|
|
1033
1030
|
const version = 3
|
|
1034
1031
|
const fetchData = {
|
|
1035
1032
|
time: new Date().getTime().toString(),
|
|
@@ -7,6 +7,8 @@ const QQ_GROUP_AVATAR_PLATFORMS = new Set([
|
|
|
7
7
|
'llob',
|
|
8
8
|
])
|
|
9
9
|
|
|
10
|
+
export const DEFAULT_AVATAR = '/img/icons/icon.svg'
|
|
11
|
+
|
|
10
12
|
// 仅用于白名单平台的 QQ 群头像,不能用于用户头像
|
|
11
13
|
export function buildQqGroupAvatar(
|
|
12
14
|
platform: string,
|
|
@@ -24,8 +26,21 @@ export function buildQqGroupAvatar(
|
|
|
24
26
|
return `https://p.qlogo.cn/gh/${rawId}/${rawId}/${safeSize}`
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
// 命中头像上下文后,任何加载失败都统一换默认 SVG
|
|
30
|
+
export function isAvatarImage(img: HTMLImageElement): boolean {
|
|
31
|
+
if (img.hasAttribute('data-avatar') || img.getAttribute('name') === 'avatar') return true
|
|
32
|
+
if (img.dataset.avatarFallback === 'true') return true
|
|
33
|
+
if (img.closest('[data-avatar], .avatar, [class*="avatar"], [name="avatar"]')) return true
|
|
34
|
+
const source = img.currentSrc || img.src || img.getAttribute('src') || ''
|
|
35
|
+
return !source ? false : /(?:q\.qlogo\.cn|p\.qlogo\.cn|\/qqapp\/)/i.test(source)
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export function avatarError(event: Event) {
|
|
28
|
-
const img = event.target
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
const img = event.target
|
|
40
|
+
if (!(img instanceof HTMLImageElement)) return
|
|
41
|
+
const fallback = new URL(DEFAULT_AVATAR, location.origin).href
|
|
42
|
+
const source = img.currentSrc || img.src
|
|
43
|
+
if (!source || source === fallback || img.dataset.avatarFallback === 'true') return
|
|
44
|
+
img.dataset.avatarFallback = 'true'
|
|
45
|
+
img.src = fallback
|
|
31
46
|
}
|
|
@@ -20,11 +20,14 @@ import { useContactStore } from '../../state/contact'
|
|
|
20
20
|
import { useUIStore } from '../../state/ui'
|
|
21
21
|
import { useAuthStore } from '../../state/auth'
|
|
22
22
|
import { useChatStore } from '../../state/chat'
|
|
23
|
-
import {
|
|
24
|
-
findSessionContact,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
import {
|
|
24
|
+
findSessionContact,
|
|
25
|
+
getSessionAliases,
|
|
26
|
+
getSessionDedupKey,
|
|
27
|
+
getSessionId,
|
|
28
|
+
mergeSessionState,
|
|
29
|
+
normalizeSessionId,
|
|
30
|
+
} from './sessionUtil'
|
|
28
31
|
|
|
29
32
|
const logger = new Logger()
|
|
30
33
|
|
|
@@ -787,8 +790,9 @@ export function updateLastestHistory(item: UserFriendElem & UserGroupElem) {
|
|
|
787
790
|
const authStore = useAuthStore()
|
|
788
791
|
// 发起获取历史消息请求
|
|
789
792
|
const type = item.user_id ? 'user' : 'group'
|
|
790
|
-
const id = item.user_id ? item.user_id : item.group_id
|
|
791
|
-
|
|
793
|
+
const id = item.user_id ? item.user_id : item.group_id
|
|
794
|
+
const sessionId = normalizeSessionId(getSessionId(item))
|
|
795
|
+
let name
|
|
792
796
|
if (authStore.jsonMap.message_list && type != 'group') {
|
|
793
797
|
name = authStore.jsonMap.message_list.private_name
|
|
794
798
|
} else {
|
|
@@ -804,7 +808,7 @@ export function updateLastestHistory(item: UserFriendElem & UserGroupElem) {
|
|
|
804
808
|
message_id: 0,
|
|
805
809
|
count: 1,
|
|
806
810
|
},
|
|
807
|
-
'getChatHistoryOnMsg_' +
|
|
811
|
+
'getChatHistoryOnMsg_' + sessionId,
|
|
808
812
|
)
|
|
809
813
|
}
|
|
810
814
|
|
|
@@ -817,47 +821,75 @@ function getSessionSortName(item: UserFriendElem & UserGroupElem) {
|
|
|
817
821
|
return item.py_start ?? getShowName(item.group_name ?? item.nickname ?? '', item.remark ?? '')
|
|
818
822
|
}
|
|
819
823
|
|
|
820
|
-
function normalizeBaseSessionMap() {
|
|
821
|
-
const contactStore = useContactStore()
|
|
822
|
-
let needNormalize = false
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
824
|
+
function normalizeBaseSessionMap() {
|
|
825
|
+
const contactStore = useContactStore()
|
|
826
|
+
let needNormalize = false
|
|
827
|
+
const seen = new Map<string, UserFriendElem & UserGroupElem>()
|
|
828
|
+
for (const [rawKey, item] of contactStore.baseOnMsgList) {
|
|
829
|
+
const id = getSessionDedupKey(item)
|
|
830
|
+
if (id.endsWith(':0') || id.endsWith(':')) continue
|
|
831
|
+
const existing = seen.get(id)
|
|
832
|
+
if (existing && existing !== item) {
|
|
833
|
+
needNormalize = true
|
|
834
|
+
continue
|
|
835
|
+
}
|
|
836
|
+
seen.set(id, item)
|
|
837
|
+
if (
|
|
838
|
+
typeof rawKey !== 'string' ||
|
|
839
|
+
!getSessionAliases(item).includes(normalizeSessionId(rawKey))
|
|
840
|
+
) {
|
|
841
|
+
needNormalize = true
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (!needNormalize) return
|
|
845
|
+
|
|
846
|
+
const canonical = new Map<string, UserFriendElem & UserGroupElem>()
|
|
847
|
+
contactStore.baseOnMsgList.forEach((item) => {
|
|
848
|
+
const id = getSessionDedupKey(item)
|
|
849
|
+
if (id === 'group:0' || id === 'group:' || id === 'user:0' || id === 'user:') return
|
|
850
|
+
const existing = canonical.get(id)
|
|
851
|
+
if (existing && existing !== item) {
|
|
852
|
+
canonical.set(id, mergeSessionState(existing, item))
|
|
853
|
+
} else if (!existing) {
|
|
854
|
+
canonical.set(id, item)
|
|
855
|
+
}
|
|
856
|
+
})
|
|
857
|
+
contactStore.baseOnMsgList.clear()
|
|
858
|
+
for (const [id, item] of canonical) {
|
|
859
|
+
for (const alias of getSessionAliases(item)) {
|
|
860
|
+
contactStore.baseOnMsgList.set(alias, item)
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function getSessionList() {
|
|
866
|
+
const contactStore = useContactStore()
|
|
867
|
+
const settingsStore = useSettingsStore()
|
|
868
|
+
const sessionMap = new Map<string, UserFriendElem & UserGroupElem>()
|
|
869
|
+
|
|
870
|
+
const addSession = (item: UserFriendElem & UserGroupElem) => {
|
|
871
|
+
const id = getSessionDedupKey(item)
|
|
872
|
+
if (id === 'group:0' || id === 'group:' || id === 'user:0' || id === 'user:') return
|
|
873
|
+
const existing = sessionMap.get(id)
|
|
874
|
+
if (existing && existing !== item) {
|
|
875
|
+
sessionMap.set(id, mergeSessionState(existing, item))
|
|
876
|
+
} else if (!existing) {
|
|
877
|
+
sessionMap.set(id, item)
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (settingsStore.sysConfig.session_display_mode === 'all') {
|
|
882
|
+
contactStore.userList.forEach((item) => {
|
|
883
|
+
addSession(item)
|
|
884
|
+
})
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
contactStore.baseOnMsgList.forEach((item) => {
|
|
888
|
+
addSession(item)
|
|
889
|
+
})
|
|
890
|
+
|
|
891
|
+
return [...sessionMap.values()]
|
|
892
|
+
}
|
|
861
893
|
|
|
862
894
|
/**
|
|
863
895
|
* 刷新消息列表排序
|
|
@@ -890,9 +922,11 @@ export function updateBaseOnMsgList() {
|
|
|
890
922
|
let groupAssistList = [] as any[]
|
|
891
923
|
if (settingsStore.sysConfig.bubble_sort_user) {
|
|
892
924
|
// 将 normalList 进行拆分
|
|
893
|
-
const shouldShowInMainList = (item: UserFriendElem & UserGroupElem) => {
|
|
894
|
-
return item.
|
|
895
|
-
|
|
925
|
+
const shouldShowInMainList = (item: UserFriendElem & UserGroupElem) => {
|
|
926
|
+
return item.group_id
|
|
927
|
+
? item.always_top
|
|
928
|
+
: Boolean(item.user_id) || item.always_top
|
|
929
|
+
}
|
|
896
930
|
onMsgList = topList.concat(normalList.filter((item) => {
|
|
897
931
|
return shouldShowInMainList(item)
|
|
898
932
|
}))
|