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.
Files changed (194) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/env.d.ts +56 -0
  3. package/client/web/index.html +21 -0
  4. package/client/web/package-lock.json +3419 -0
  5. package/client/web/package.json +62 -0
  6. package/client/web/public/bcui/css/color-dark.css +35 -0
  7. package/client/web/public/bcui/css/color-light.css +35 -0
  8. package/client/web/public/bcui/css/style.css +468 -0
  9. package/client/web/public/bcui/js/main.js +193 -0
  10. package/client/web/public/css/append-dark.css +13 -0
  11. package/client/web/public/css/append-light.css +13 -0
  12. package/client/web/public/css/append-napcat-dark.css +14 -0
  13. package/client/web/public/css/append-napcat-light.css +14 -0
  14. package/client/web/public/favicon.ico +0 -0
  15. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  16. package/client/web/public/img/icons/icon-maskable.png +0 -0
  17. package/client/web/public/img/icons/icon.png +0 -0
  18. package/client/web/public/img/icons/icon.svg +10 -0
  19. package/client/web/public/robots.txt +2 -0
  20. package/client/web/public/sw.js +58 -0
  21. package/client/web/scripts/copy-bcui.mjs +17 -0
  22. package/client/web/src/App.vue +1127 -0
  23. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  24. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  25. package/client/web/src/assets/css/append/append_linux.css +125 -0
  26. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  27. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  28. package/client/web/src/assets/css/append/append_new.css +843 -0
  29. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  30. package/client/web/src/assets/css/append/append_win32.css +109 -0
  31. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  32. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  33. package/client/web/src/assets/css/chat.css +2104 -0
  34. package/client/web/src/assets/css/msg.css +941 -0
  35. package/client/web/src/assets/css/options.css +811 -0
  36. package/client/web/src/assets/css/sys_notice.css +62 -0
  37. package/client/web/src/assets/css/view.css +2105 -0
  38. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  39. package/client/web/src/assets/img/Vue.png +0 -0
  40. package/client/web/src/assets/img/cakes.svg +189 -0
  41. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  42. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  43. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  44. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  45. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  46. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  47. package/client/web/src/assets/img/icons/exit.png +0 -0
  48. package/client/web/src/assets/img/icons/pen.png +0 -0
  49. package/client/web/src/assets/img/stars.svg +89 -0
  50. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  51. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  52. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  53. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  54. package/client/web/src/assets/logo.png +0 -0
  55. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  56. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  57. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  58. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  59. package/client/web/src/assets/qed.txt +1 -0
  60. package/client/web/src/components/AboutPan.vue +197 -0
  61. package/client/web/src/components/BulletinBody.vue +129 -0
  62. package/client/web/src/components/DepPan.vue +310 -0
  63. package/client/web/src/components/EmojiFace.vue +32 -0
  64. package/client/web/src/components/FacePan.vue +461 -0
  65. package/client/web/src/components/FileBody.vue +129 -0
  66. package/client/web/src/components/FileManager.vue +532 -0
  67. package/client/web/src/components/FriendBody.vue +54 -0
  68. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  69. package/client/web/src/components/History.vue +49 -0
  70. package/client/web/src/components/LazyLottie.vue +82 -0
  71. package/client/web/src/components/MergePan.vue +127 -0
  72. package/client/web/src/components/MsgBody.vue +1478 -0
  73. package/client/web/src/components/MusicPlayer.vue +549 -0
  74. package/client/web/src/components/NoticeBody.vue +168 -0
  75. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  76. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  77. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  78. package/client/web/src/components/TinySessionBody.vue +51 -0
  79. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  80. package/client/web/src/components/UpdatePan.vue +263 -0
  81. package/client/web/src/components/ViewerCom.vue +1360 -0
  82. package/client/web/src/components/VoiceMsg.vue +626 -0
  83. package/client/web/src/components/WelPan.vue +894 -0
  84. package/client/web/src/components/msg-body/.gitkeep +0 -0
  85. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  86. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  87. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  88. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  89. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  90. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  91. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  92. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  93. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  94. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  95. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  96. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  97. package/client/web/src/components/notice-component/.gitkeep +0 -0
  98. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  99. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  100. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  101. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  102. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  103. package/client/web/src/function/base.ts +238 -0
  104. package/client/web/src/function/connect.ts +2042 -0
  105. package/client/web/src/function/debug.ts +10 -0
  106. package/client/web/src/function/elements/information.ts +141 -0
  107. package/client/web/src/function/elements/system.ts +101 -0
  108. package/client/web/src/function/elements/vueComp.ts +62 -0
  109. package/client/web/src/function/favicon.ts +76 -0
  110. package/client/web/src/function/input.ts +69 -0
  111. package/client/web/src/function/model/emoji.ts +127 -0
  112. package/client/web/src/function/model/img.ts +150 -0
  113. package/client/web/src/function/model/msg-body.ts +51 -0
  114. package/client/web/src/function/msg.ts +2674 -0
  115. package/client/web/src/function/notify.ts +225 -0
  116. package/client/web/src/function/option.ts +749 -0
  117. package/client/web/src/function/satori-model.ts +818 -0
  118. package/client/web/src/function/satori.ts +325 -0
  119. package/client/web/src/function/sender.ts +178 -0
  120. package/client/web/src/function/tooltip.ts +49 -0
  121. package/client/web/src/function/utils/appUtil.ts +2288 -0
  122. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  123. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  124. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  125. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  126. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  127. package/client/web/src/function/utils/pinyin.ts +141 -0
  128. package/client/web/src/function/utils/recordUtil.ts +187 -0
  129. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  130. package/client/web/src/function/utils/systemUtil.ts +664 -0
  131. package/client/web/src/main.ts +91 -0
  132. package/client/web/src/pages/Chat.vue +3453 -0
  133. package/client/web/src/pages/Friends.vue +636 -0
  134. package/client/web/src/pages/Info.vue +717 -0
  135. package/client/web/src/pages/Messages.vue +875 -0
  136. package/client/web/src/pages/Options.vue +82 -0
  137. package/client/web/src/pages/Qzone.vue +1178 -0
  138. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  139. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  140. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  141. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  142. package/client/web/src/pages/options/OptAccount.vue +107 -0
  143. package/client/web/src/pages/options/OptAddon.vue +185 -0
  144. package/client/web/src/pages/options/OptDev.vue +704 -0
  145. package/client/web/src/pages/options/OptFunction.vue +403 -0
  146. package/client/web/src/pages/options/OptInfo.vue +165 -0
  147. package/client/web/src/pages/options/OptView.vue +846 -0
  148. package/client/web/src/runtime/backend.ts +310 -0
  149. package/client/web/src/state/auth.ts +14 -0
  150. package/client/web/src/state/chat.ts +36 -0
  151. package/client/web/src/state/connection.ts +20 -0
  152. package/client/web/src/state/contact.ts +37 -0
  153. package/client/web/src/state/qzone.ts +18 -0
  154. package/client/web/src/state/sessionHistory.ts +25 -0
  155. package/client/web/src/state/settings.ts +18 -0
  156. package/client/web/src/state/sticker.ts +10 -0
  157. package/client/web/src/state/ui.ts +56 -0
  158. package/client/web/tsconfig.json +27 -0
  159. package/client/web/tsconfig.tsbuildinfo +1 -0
  160. package/client/web/vite.config.ts +33 -0
  161. package/dist/index.js +1 -12
  162. package/dist/style.css +1 -1
  163. package/lib/index.js +2070 -2077
  164. package/package.json +15 -2
  165. package/readme.md +0 -22
  166. package/src/bootstrap.ts +71 -0
  167. package/src/cache.ts +319 -0
  168. package/src/config.ts +23 -35
  169. package/src/database.ts +650 -0
  170. package/src/index.ts +27 -59
  171. package/src/media.ts +302 -0
  172. package/src/recorder.ts +175 -0
  173. package/src/self-message.ts +456 -0
  174. package/src/server.d.ts +26 -0
  175. package/src/types.ts +108 -48
  176. package/src/web.ts +712 -0
  177. package/client/vue/chat-logic.ts +0 -683
  178. package/client/vue/composables/useChatActions.ts +0 -61
  179. package/client/vue/composables/useChatData.ts +0 -185
  180. package/client/vue/composables/useImageCache.ts +0 -281
  181. package/client/vue/composables/useVideoCache.ts +0 -125
  182. package/client/vue/types.ts +0 -72
  183. package/lib/api-handlers.d.ts +0 -25
  184. package/lib/config.d.ts +0 -14
  185. package/lib/file-manager.d.ts +0 -124
  186. package/lib/index.d.ts +0 -17
  187. package/lib/logger.d.ts +0 -9
  188. package/lib/message-handler.d.ts +0 -35
  189. package/lib/types.d.ts +0 -57
  190. package/lib/utils.d.ts +0 -18
  191. package/src/api-handlers.ts +0 -646
  192. package/src/file-manager.ts +0 -1436
  193. package/src/message-handler.ts +0 -507
  194. package/src/utils.ts +0 -189
@@ -0,0 +1,875 @@
1
+ <!--
2
+ * @FileDescription: 消息列表页面
3
+ * @Author: Stapxs
4
+ * @Date:
5
+ * 2022/08/14
6
+ * 2022/12/14
7
+ * @Version:
8
+ * 1.0 - 初始版本
9
+ * 1.5 - 重构为 ts 版本,代码格式优化
10
+ -->
11
+
12
+ <template>
13
+ <div class="friend-view">
14
+ <div class="friend-list-container">
15
+ <div id="message-list"
16
+ :class="'friend-list' +
17
+ (uiStore.openSideBar ? ' open' : '') +
18
+ (showGroupAssist ? ' show' : '')">
19
+ <div>
20
+ <div class="base only">
21
+ <span>{{ $t('消息') }}</span>
22
+ <div style="flex: 1" />
23
+ <font-awesome-icon
24
+ :icon="['fas', 'clock-rotate-left']"
25
+ @click="openHistory" />
26
+ <font-awesome-icon :icon="['fas', 'trash-can']" @click="cleanList" />
27
+ </div>
28
+ <div class="small">
29
+ <span>{{ $t('消息') }}</span>
30
+ <div v-if="showGroupAssist"
31
+ style="margin-right: -5px;margin-left: 5px;"
32
+ @click="showGroupAssist = !showGroupAssist">
33
+ <font-awesome-icon :icon="['fas', 'angle-left']" />
34
+ </div>
35
+ <div @click="openLeftBar">
36
+ <font-awesome-icon :icon="['fas', 'bars-staggered']" />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <div id="message-list-body" class="robot-accordion-list">
41
+ <div v-for="bot in satoriLogins" :key="'bot-' + bot.selfId"
42
+ :class="['robot-accordion', { expanded: bot.selfId === activeBotId }]">
43
+ <div class="robot-accordion-header" @click="toggleRobot(bot)">
44
+ <img :src="bot.avatar || '/img/icons/icon.svg'" :alt="bot.name" @error="avatarError">
45
+ <div>
46
+ <span :title="bot.name">{{ bot.name }}</span>
47
+ <small>{{ bot.platform }}</small>
48
+ </div>
49
+ <font-awesome-icon :icon="['fas', bot.selfId === activeBotId ? 'angle-up' : 'angle-down']" />
50
+ </div>
51
+ <div v-if="bot.selfId === activeBotId" class="robot-accordion-content">
52
+ <div v-if="!showGroupAssist &&
53
+ !contactStore.systemNoticesList &&
54
+ (!contactStore.groupAssistList || contactStore.groupAssistList.length === 0) &&
55
+ contactStore.onMsgList.length === 0"
56
+ class="empty-state">
57
+ {{ $t('空') }}
58
+ </div>
59
+ <TransitionGroup
60
+ name="onmsg"
61
+ tag="div"
62
+ :class="uiStore.openSideBar ? ' open' : ''"
63
+ style="overflow-x: hidden">
64
+ <!-- 系统信息 -->
65
+ <FriendBody v-if="!showGroupAssist &&
66
+ contactStore.systemNoticesList &&
67
+ Object.keys(contactStore.systemNoticesList).length > 0"
68
+ key="inMessage--10000"
69
+ :select="chat.show.id === -10000"
70
+ :menu="menu.select && menu.select.user_id === -10000"
71
+ :data="{
72
+ user_id: -10000,
73
+ always_top: true,
74
+ nickname: $t('系统通知'),
75
+ remark: $t('系统通知'),
76
+ raw_msg: contactStore.systemNoticesList[0].comment
77
+ }"
78
+ @click="systemNoticeClick"
79
+ @contextmenu.prevent="systemNoticeMenuShow($event)"
80
+ @touchstart="systemNoticeMenuStart($event)"
81
+ @touchmove="showMenuMove"
82
+ @touchend="showMenuEnd" />
83
+ <!--- 群组消息 -->
84
+ <FriendBody
85
+ v-if="contactStore.groupAssistList && contactStore.groupAssistList.length > 0"
86
+ key="inMessage--10001"
87
+ :select="chat.show.id === -10001"
88
+ :data="{
89
+ user_id: -10001,
90
+ always_top: true,
91
+ nickname: $t('群收纳盒'),
92
+ remark: $t('群收纳盒'),
93
+ time: contactStore.groupAssistList[0].time,
94
+ raw_msg: contactStore.groupAssistList[0].group_name + ': ' +
95
+ (contactStore.groupAssistList[0].raw_msg_base ?? '')
96
+ }"
97
+ @click="showGroupAssistCheck" />
98
+ <!-- 其他消息 -->
99
+ <FriendBody
100
+ v-for="item in contactStore.onMsgList"
101
+ :key="'inMessage-' + (item.user_id ? item.user_id : item.group_id)"
102
+ :select="chat.show.id === item.user_id || (chat.show.id === item.group_id && chat.group_name != '')"
103
+ :menu="menu.select && menu.select == item"
104
+ :data="item"
105
+ from="message"
106
+ @contextmenu.prevent="listMenuShow($event, item)"
107
+ @click="userClick(item)"
108
+ @touchstart="showMenuStart($event, item)"
109
+ @touchmove="showMenuMove"
110
+ @touchend="showMenuEnd" />
111
+ </TransitionGroup>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ <div id="group-assist-message-list"
117
+ :class="'friend-list group-assist-message-list' +
118
+ (uiStore.openSideBar ? ' open' : '') +
119
+ (showGroupAssist ? ' show' : '')">
120
+ <div>
121
+ <div class="base only">
122
+ <span style="cursor: pointer;"
123
+ @click="showGroupAssist = !showGroupAssist">
124
+ <font-awesome-icon style="margin-right: 5px;" :icon="['fas', 'angle-left']" />
125
+ {{ $t('群收纳盒') }}
126
+ </span>
127
+ <a v-if="contactStore.newMsgCount > 0">{{ contactStore.newMsgCount }}</a>
128
+ </div>
129
+ <div class="small">
130
+ <span style="cursor: pointer;">
131
+ {{ $t('群收纳盒') }}
132
+ <a v-if="contactStore.newMsgCount > 0">{{ contactStore.newMsgCount }}</a>
133
+ </span>
134
+ <div v-if="showGroupAssist"
135
+ style="margin-right: -5px;margin-left: 5px;"
136
+ @click="showGroupAssist = !showGroupAssist">
137
+ <font-awesome-icon :icon="['fas', 'angle-left']" />
138
+ </div>
139
+ <div @click="openLeftBar">
140
+ <font-awesome-icon :icon="['fas', 'bars-staggered']" />
141
+ </div>
142
+ </div>
143
+ </div>
144
+ <TransitionGroup
145
+ id="group-assist-message-list-body"
146
+ name="onmsg"
147
+ tag="div"
148
+ :class="uiStore.openSideBar ? ' open' : ''"
149
+ style="overflow-x: hidden">
150
+ <!-- 其他消息 -->
151
+ <FriendBody
152
+ v-for="item in contactStore.groupAssistList"
153
+ :key="'inMessage-' + (item.user_id ? item.user_id : item.group_id)"
154
+ :select="chat.show.id === item.user_id || (chat.show.id === item.group_id && chat.group_name != '')"
155
+ :menu="menu.select && menu.select == item"
156
+ :data="item"
157
+ from="message"
158
+ @contextmenu.prevent="listMenuShow($event, item)"
159
+ @click="userClick(item)"
160
+ @touchstart="showMenuStart($event, item)"
161
+ @touchmove="showMenuMove"
162
+ @touchend="showMenuEnd" />
163
+ </TransitionGroup>
164
+ </div>
165
+ </div>
166
+ <BcMenu :data="listMenu" name="messages-menu"
167
+ @close="listMenuClose">
168
+ <ul>
169
+ <li id="top" icon="fa-solid fa-thumbtack">
170
+ {{ $t('置顶') }}
171
+ </li>
172
+ <li id="canceltop" icon="fa-solid fa-grip-lines">
173
+ {{ $t('取消置顶') }}
174
+ </li>
175
+ <li id="remove" icon="fa-solid fa-trash-can">
176
+ {{ $t('删除') }}
177
+ </li>
178
+ <li id="readed" icon="fa-solid fa-check-to-slot">
179
+ {{ $t('标记已读') }}
180
+ </li>
181
+ <li id="read" icon="fa-solid fa-flag">
182
+ {{ $t('标记未读') }}
183
+ </li>
184
+ <li id="clear_system_notice" icon="fa-solid fa-broom">
185
+ {{ $t('清空通知') }}
186
+ </li>
187
+ </ul>
188
+ </BcMenu>
189
+ <div :class="'friend-list-space' + (uiStore.openSideBar ? ' open' : '')">
190
+ <div v-if="!loginInfo.status || chatStore.chatInfo.show.id == 0" class="ss-card">
191
+ <font-awesome-icon :icon="['fas', 'inbox']" />
192
+ <span>{{ $t('选择联系人开始聊天') }}</span>
193
+ </div>
194
+ <div v-else-if="chatStore.messageList.length > 0" class="ss-card cd">
195
+ <font-awesome-icon :icon="['fas', 'angles-right']" />
196
+ <span>(っ≧ω≦)っ</span>
197
+ <span>{{ $t('别划了别划了被看见了啦') }}</span>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </template>
202
+
203
+ <script setup lang="ts">
204
+ import { computed, ref, onMounted, markRaw, watch, nextTick } from 'vue'
205
+ import { i18n } from '../main'
206
+ import FriendBody from '../components/FriendBody.vue'
207
+ import BcMenu from 'vue3-bcui/packages/bc-menu'
208
+ import Menu from 'vue3-bcui/packages/bc-menu/index'
209
+ import Option from '../function/option'
210
+
211
+ import { useSettingsStore } from '../state/settings'
212
+ import {
213
+ UserFriendElem,
214
+ UserGroupElem,
215
+ } from '../function/elements/information'
216
+ import { PopInfo, PopType } from '../function/base'
217
+ import { MenuStatue } from 'vue3-bcui/packages/dist/types'
218
+ import { library } from '@fortawesome/fontawesome-svg-core'
219
+ import { login as loginInfo } from '../function/connect'
220
+ import { Connector, flushPendingBotEvents, loadContactsFromCache, restoreBotStateFromMessageCache } from '../function/connect'
221
+ import { getActiveBot, getLogins, setActiveBot } from '../function/satori'
222
+ import { getShowName, updateBaseOnMsgList } from '../function/utils/msgUtil'
223
+
224
+ import {
225
+ faThumbTack,
226
+ faTrashCan,
227
+ faCheckToSlot,
228
+ faGripLines,
229
+ faBroom,
230
+ } from '@fortawesome/free-solid-svg-icons'
231
+ import { Notify } from '../function/notify'
232
+ import { refreshFavicon } from '../function/favicon'
233
+ import { backend } from '../runtime/backend'
234
+ import History from '../components/History.vue'
235
+ import { normalizeSessionId } from '../function/utils/sessionUtil'
236
+ import { avatarError } from '../function/utils/avatarUtil'
237
+ import { useUIStore } from '../state/ui'
238
+ import { useAuthStore } from '../state/auth'
239
+ import { useContactStore } from '../state/contact'
240
+ import { useChatStore } from '../state/chat'
241
+
242
+ const $t = i18n.global.t
243
+
244
+ defineOptions({ name: 'VueMessages' })
245
+
246
+ const uiStore = useUIStore()
247
+ const authStore = useAuthStore()
248
+ const contactStore = useContactStore()
249
+ const chatStore = useChatStore()
250
+ const settingsStore = useSettingsStore()
251
+ const satoriLogins = ref<Array<{ platform: string; selfId: string; name: string; avatar?: string; status?: number; features?: string[] }>>(getLogins())
252
+ const activeBotId = ref(getActiveBot()?.selfId ?? loginInfo.selectedSatoriBot ?? '')
253
+
254
+ watch(() => loginInfo.satoriLogins, (list) => {
255
+ satoriLogins.value = list ?? []
256
+ if (activeBotId.value && contactStore.userList.length === 0) {
257
+ void loadContactsFromCache()
258
+ }
259
+ }, { immediate: true })
260
+
261
+ const currentBotName = computed(() => {
262
+ return satoriLogins.value.find((item) => item.selfId === activeBotId.value)?.name ?? ''
263
+ })
264
+
265
+ function backToRobots() {
266
+ snapshotCurrentBot()
267
+ activeBotId.value = ''
268
+ chatStore.messageList = []
269
+ chatStore.chatInfo.show.id = 0
270
+ }
271
+
272
+ function snapshotCurrentBot() {
273
+ const id = activeBotId.value
274
+ if (!id) return
275
+ contactStore.botStates.set(id, {
276
+ userList: [...contactStore.userList],
277
+ baseList: Array.from(contactStore.baseOnMsgList.entries()),
278
+ onMsgList: [...contactStore.onMsgList],
279
+ })
280
+ }
281
+
282
+ function restoreBotStateUi(bot: { platform: string; selfId: string }) {
283
+ const restored = contactStore.botStates.get(bot.selfId)
284
+ if (!restored) return
285
+ contactStore.baseOnMsgList.clear()
286
+ for (const [key, value] of restored.baseList) {
287
+ contactStore.baseOnMsgList.set(normalizeSessionId(key), value)
288
+ }
289
+ for (const item of restored.onMsgList) {
290
+ const id = String(item.user_id ?? item.group_id ?? '')
291
+ if (!id || id === '0') continue
292
+ contactStore.baseOnMsgList.set(
293
+ normalizeSessionId(id),
294
+ item as UserFriendElem & UserGroupElem,
295
+ )
296
+ }
297
+ contactStore.onMsgList = restored.onMsgList
298
+ }
299
+
300
+ function selectRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
301
+ if (bot.selfId === activeBotId.value) return
302
+ snapshotCurrentBot()
303
+ activeBotId.value = bot.selfId
304
+ setActiveBot(bot.platform, bot.selfId)
305
+ loginInfo.uin = bot.selfId
306
+ loginInfo.nickname = bot.name
307
+ loginInfo.platform = bot.platform
308
+ authStore.loginInfo = {
309
+ ...authStore.loginInfo,
310
+ uin: bot.selfId,
311
+ user_id: bot.selfId,
312
+ nickname: bot.name,
313
+ platform: bot.platform,
314
+ avatar: bot.avatar,
315
+ satoriLogins: satoriLogins.value,
316
+ selectedSatoriBot: bot.selfId,
317
+ }
318
+ const saved = contactStore.botStates.get(bot.selfId)
319
+ if (saved) {
320
+ contactStore.userList = saved.userList
321
+ contactStore.baseOnMsgList.clear()
322
+ for (const [key, value] of saved.baseList) {
323
+ contactStore.baseOnMsgList.set(normalizeSessionId(key), value)
324
+ }
325
+ contactStore.onMsgList = saved.onMsgList
326
+ restoreBotStateFromMessageCache(bot.platform, bot.selfId)
327
+ restoreBotStateUi(bot)
328
+ flushPendingBotEvents(bot.platform, bot.selfId)
329
+ updateBaseOnMsgList()
330
+ void loadContactsFromCache()
331
+ } else {
332
+ contactStore.userList = []
333
+ contactStore.baseOnMsgList.clear()
334
+ contactStore.onMsgList = []
335
+ chatStore.messageList = []
336
+ chatStore.chatInfo.show.id = 0
337
+ restoreBotStateFromMessageCache(bot.platform, bot.selfId)
338
+ restoreBotStateUi(bot)
339
+ flushPendingBotEvents(bot.platform, bot.selfId)
340
+ updateBaseOnMsgList()
341
+ void loadContactsFromCache()
342
+ }
343
+ }
344
+
345
+ function toggleRobot(bot: { platform: string; selfId: string; name: string; avatar?: string; features?: string[] }) {
346
+ if (activeBotId.value === bot.selfId) {
347
+ backToRobots()
348
+ } else {
349
+ selectRobot(bot)
350
+ }
351
+ }
352
+ const props = defineProps<{ chat: any }>()
353
+ const emit = defineEmits<{
354
+ userClick: [data: any]
355
+ loadHistory: [data: any]
356
+ }>()
357
+
358
+ const trRead = ref(false)
359
+ const listMenu = ref<MenuStatue>({
360
+ show: false,
361
+ point: { x: 0, y: 0 },
362
+ })
363
+ const menu = Menu.append
364
+ const showMenu = ref(false)
365
+ const showGroupAssist = ref(false)
366
+
367
+ onMounted(() => {
368
+ library.add(faCheckToSlot, faThumbTack, faTrashCan, faGripLines, faBroom)
369
+ if (activeBotId.value && contactStore.userList.length === 0) {
370
+ void loadContactsFromCache()
371
+ }
372
+ })
373
+
374
+ /**
375
+ * 联系人点击事件
376
+ * @param data 联系人对象
377
+ */
378
+ function userClick(data: UserFriendElem & UserGroupElem) {
379
+ const id = data.user_id ? data.user_id : data.group_id
380
+ if (id === undefined || id === null || String(id) === '' || String(id) === '0') return
381
+ if (!trRead.value && id != props.chat.show.id) {
382
+ if (uiStore.openSideBar) {
383
+ openLeftBar()
384
+ }
385
+ const back = {
386
+ // 临时会话标志
387
+ temp: data.group_name == '' ? data.group_id : undefined,
388
+ type: data.user_id ? 'user' : 'group',
389
+ id: id,
390
+ name: getShowName(data.group_name || data.nickname, data.remark),
391
+ avatar: data.avatar || '/img/icons/icon.svg',
392
+ channel_id: data.channel_id ?? data.channelId,
393
+ guild_id: data.guild_id ?? data.guildId,
394
+ }
395
+ if (props.chat.id != back.id) {
396
+ // 更新聊天框
397
+ emit('userClick', back)
398
+ // 获取历史消息
399
+ if(!uiStore.nowGetHistory) {
400
+ emit('loadHistory', back)
401
+ }
402
+ }
403
+ // 清除新消息标记
404
+ const item = contactStore.baseOnMsgList.get(normalizeSessionId(id))
405
+ if(item) {
406
+ if(item.new_msg) {
407
+ item.new_msg = false
408
+ contactStore.newMsgCount--
409
+ }
410
+ item.highlight = undefined
411
+ contactStore.baseOnMsgList.set(normalizeSessionId(id), item)
412
+ // 关闭所有通知
413
+ new Notify().closeAll((item.group_id ?? item.user_id).toString())
414
+ }
415
+ }
416
+ }
417
+
418
+ /**
419
+ * 显示系统通知菜单
420
+ * @param event 鼠标事件
421
+ */
422
+ function systemNoticeMenuShow(event: Event) {
423
+ const info = menu.set('messages-menu', event as MouseEvent)
424
+ showMenu.value = false
425
+ info.list = ['clear_system_notice']
426
+ listMenu.value = info
427
+ menu.select = { user_id: -10000 }
428
+ }
429
+
430
+ /**
431
+ * 系统通知菜单长按开始
432
+ */
433
+ function systemNoticeMenuStart(event: TouchEvent) {
434
+ showMenuStart(event, { user_id: -10000 } as any)
435
+ }
436
+
437
+ /**
438
+ * 清空系统通知
439
+ */
440
+ function clearSystemNotices() {
441
+ contactStore.systemNoticesList = []
442
+ new PopInfo().add(
443
+ PopType.INFO,
444
+ $t('已清空系统通知'),
445
+ )
446
+ }
447
+
448
+ /**
449
+ * 系统通知点击事件
450
+ */
451
+ function systemNoticeClick() {
452
+ if (uiStore.openSideBar) {
453
+ openLeftBar()
454
+ }
455
+ const back = {
456
+ type: 'user',
457
+ id: -10000,
458
+ name: '系统消息',
459
+ }
460
+ emit('userClick', back)
461
+ }
462
+
463
+ /**
464
+ * 侧边栏操作
465
+ */
466
+ function openLeftBar() {
467
+ uiStore.openSideBar = !uiStore.openSideBar
468
+ }
469
+
470
+ /**
471
+ * 标记群组消息为已读
472
+ */
473
+ function readMsg(data: UserFriendElem & UserGroupElem) {
474
+ const id = data.group_id ? data.group_id : data.user_id
475
+ const item = contactStore.baseOnMsgList.get(normalizeSessionId(id))
476
+ if(item) {
477
+ if(item.new_msg) {
478
+ item.new_msg = false
479
+ contactStore.newMsgCount--
480
+ }
481
+ item.highlight = undefined
482
+ contactStore.baseOnMsgList.set(normalizeSessionId(id), item)
483
+ }
484
+ // pop
485
+ new PopInfo().add(
486
+ PopType.INFO,
487
+ $t('已标记为已读'),
488
+ )
489
+ }
490
+
491
+ /**
492
+ * 清空消息列表
493
+ */
494
+ function cleanList() {
495
+ // 刷新置顶列表
496
+ const info = settingsStore.sysConfig.top_info as {
497
+ [key: string]: number[]
498
+ } | null
499
+ contactStore.baseOnMsgList.clear()
500
+ contactStore.onMsgList = []
501
+ contactStore.groupAssistList = []
502
+ contactStore.systemNoticesList = undefined
503
+ contactStore.newMsgCount = 0
504
+ for (const state of contactStore.botStates.values()) {
505
+ state.baseList = []
506
+ state.onMsgList = []
507
+ }
508
+ if (info != null) {
509
+ const topList = info[authStore.loginInfo.uin]
510
+ if (topList !== undefined) {
511
+ contactStore.userList.forEach((item) => {
512
+ const id = Number(
513
+ item.user_id ? item.user_id : item.group_id,
514
+ )
515
+ if (topList.indexOf(id) >= 0) {
516
+ item.always_top = true
517
+ contactStore.baseOnMsgList.set(normalizeSessionId(id), item)
518
+ }
519
+ })
520
+ }
521
+ }
522
+ // 刷新 favicon
523
+ refreshFavicon()
524
+ }
525
+
526
+ /**
527
+ * 列表菜单关闭事件
528
+ * @param id 选择的菜单 ID
529
+ */
530
+ function listMenuClose(id: string) {
531
+ const menuEl = document.getElementById(
532
+ 'msg-menu-view-messages-menu',
533
+ )?.children[1] as HTMLDivElement
534
+ if (menuEl) {
535
+ setTimeout(() => {
536
+ menuEl.style.transition = 'transform .1s'
537
+ }, 200)
538
+ }
539
+ listMenu.value.show = false
540
+ const item = menu.select
541
+ if (id) {
542
+ switch (id) {
543
+ case 'read': {
544
+ if(!item.new_msg) {
545
+ item.new_msg = true
546
+ contactStore.newMsgCount++
547
+ }
548
+ break
549
+ }
550
+ case 'readed':
551
+ readMsg(item)
552
+ // 刷新 favicon
553
+ refreshFavicon()
554
+ break
555
+ case 'remove': {
556
+ const id = item.user_id ? item.user_id : item.group_id
557
+ contactStore.baseOnMsgList.delete(normalizeSessionId(id))
558
+ refreshFavicon()
559
+ break
560
+ }
561
+ case 'top':
562
+ saveTop(item, true)
563
+ break
564
+ case 'canceltop':
565
+ saveTop(item, false)
566
+ break
567
+ case 'clear_system_notice': {
568
+ clearSystemNotices()
569
+ break
570
+ }
571
+ }
572
+ }
573
+ menu.select = undefined
574
+ }
575
+
576
+ /**
577
+ * 保存置顶信息
578
+ * @param item 菜单选中项
579
+ * @param value 是否置顶
580
+ */
581
+ function saveTop(item: any, value: boolean) {
582
+ const id = authStore.loginInfo.uin
583
+ const upId = item.user_id ? item.user_id : item.group_id
584
+ // 完整的设置 JSON
585
+ let topInfo = settingsStore.sysConfig.top_info as {
586
+ [key: string]: number[]
587
+ }
588
+ if (topInfo == null || typeof topInfo !== 'object') {
589
+ topInfo = {}
590
+ }
591
+ // 本人的置顶信息
592
+ let topList = topInfo[id]
593
+ // 操作
594
+ if (value) {
595
+ if (topList) {
596
+ if (topList.indexOf(props.chat.show.id) < 0) {
597
+ topList.push(upId)
598
+ }
599
+ } else {
600
+ topList = [upId]
601
+ }
602
+ } else {
603
+ if (topList) {
604
+ topList.splice(topList.indexOf(upId), 1)
605
+ }
606
+ }
607
+ // 刷新设置
608
+ if (topList) {
609
+ topInfo[id] = topList
610
+ Option.save('top_info', topInfo)
611
+ }
612
+ // 为消息列表内的对象刷新置顶标志
613
+ item.always_top = value
614
+ // 刷新群收纳盒
615
+ if(item.group_id && settingsStore.sysConfig.bubble_sort_user) {
616
+ if(value) {
617
+ showGroupAssist.value = false
618
+ } else {
619
+ showGroupAssist.value = true
620
+ }
621
+ }
622
+ }
623
+
624
+ /**
625
+ * 显示列表菜单
626
+ * @param item 菜单内容
627
+ */
628
+ function listMenuShow(event: Event, item: UserFriendElem & UserGroupElem) {
629
+ const info = menu.set('messages-menu', event as MouseEvent)
630
+ listMenuShowRun(info, item)
631
+ }
632
+
633
+ function listMenuShowRun(info: any, item: UserFriendElem & UserGroupElem) {
634
+ // PS:这是触屏触发的标志,如果优先触发了 contextmenu 就不用触发触屏了
635
+ showMenu.value = false
636
+ info.list = ['top', 'remove']
637
+ // 置顶的不显示移除
638
+ if (item.always_top) {
639
+ info.list = ['canceltop']
640
+ }
641
+ if (item.new_msg) {
642
+ info.list.push('readed')
643
+ } else {
644
+ info.list.push('read')
645
+ }
646
+ listMenu.value = info
647
+ menu.select = item
648
+ // 出界处理
649
+ // 菜单显示后再测量,避免固定延迟
650
+ nextTick(() => {
651
+ const menuEl = document.getElementById(
652
+ 'msg-menu-view-messages-menu',
653
+ )?.children[1] as HTMLDivElement
654
+ if (menuEl) {
655
+ menuEl.style.transition = 'margin .2s, transform .1s'
656
+ const hight = menuEl.clientHeight
657
+ const top = menuEl.getBoundingClientRect().top
658
+ const docHight = document.documentElement.clientHeight
659
+ // 出界高度
660
+ const dtHight = hight + top - docHight + 20
661
+ if (dtHight > 0) {
662
+ menuEl.style.marginTop = docHight - hight - 30 + 'px'
663
+ }
664
+ }
665
+ })
666
+ }
667
+
668
+ /**
669
+ * 显示群收纳盒
670
+ */
671
+ function showGroupAssistCheck() {
672
+ // 只展开收纳盒,不再默认选中第一个群
673
+ showGroupAssist.value = !showGroupAssist.value
674
+ }
675
+
676
+ function showMenuStart(
677
+ event: TouchEvent,
678
+ item: UserFriendElem & UserGroupElem,
679
+ ) {
680
+ const info = {
681
+ show: true,
682
+ point: {
683
+ x: event.targetTouches[0].pageX,
684
+ y: event.targetTouches[0].pageY,
685
+ },
686
+ }
687
+ showMenu.value = true
688
+ setTimeout(() => {
689
+ if (showMenu.value) {
690
+ listMenuShowRun(info, item)
691
+ showMenu.value = false
692
+ }
693
+ }, 500)
694
+ }
695
+
696
+ function showMenuMove() {
697
+ showMenu.value = false
698
+ }
699
+
700
+ function showMenuEnd() {
701
+ showMenu.value = false
702
+ }
703
+
704
+ function openHistory() {
705
+ const popInfo = {
706
+ template: markRaw(History),
707
+ svg: 'clock-rotate-left',
708
+ title: $t('历史记录')
709
+ }
710
+ uiStore.popBoxList.push(popInfo)
711
+ }
712
+ </script>
713
+
714
+ <style>
715
+ .robot-item {
716
+ display: flex;
717
+ align-items: center;
718
+ gap: 8px;
719
+ padding: 10px 12px;
720
+ cursor: pointer;
721
+ border-left: 3px solid transparent;
722
+ }
723
+
724
+ .robot-item:hover {
725
+ background: var(--color-hover-bg, rgba(0, 0, 0, 0.04));
726
+ }
727
+
728
+ .robot-item.active {
729
+ border-left-color: var(--color-main, #3b82f6);
730
+ background: var(--color-active-bg, rgba(59, 130, 246, 0.08));
731
+ }
732
+
733
+ .robot-item img {
734
+ width: 32px;
735
+ height: 32px;
736
+ border-radius: 50%;
737
+ object-fit: cover;
738
+ }
739
+
740
+ .robot-item div {
741
+ display: flex;
742
+ flex-direction: column;
743
+ min-width: 0;
744
+ flex: 1;
745
+ overflow: hidden;
746
+ }
747
+
748
+ .robot-item span {
749
+ display: block;
750
+ font-size: 13px;
751
+ font-weight: 600;
752
+ max-width: 100%;
753
+ white-space: nowrap;
754
+ overflow: hidden;
755
+ text-overflow: ellipsis;
756
+ -webkit-mask-image: linear-gradient(to right, black 0%, black 70%, transparent 100%);
757
+ mask-image: linear-gradient(to right, black 0%, black 70%, transparent 100%);
758
+ }
759
+
760
+ .robot-item small {
761
+ font-size: 11px;
762
+ opacity: 0.6;
763
+ }
764
+
765
+ .robot-accordion-list {
766
+ height: 100%;
767
+ overflow-y: auto;
768
+ overflow-x: hidden;
769
+ }
770
+ .robot-accordion {
771
+ border-bottom: 1px solid var(--color-card-2, rgba(0, 0, 0, 0.08));
772
+ }
773
+ .robot-accordion-header {
774
+ display: flex;
775
+ align-items: center;
776
+ gap: 8px;
777
+ padding: 10px 12px;
778
+ cursor: pointer;
779
+ }
780
+ .robot-accordion-header:hover {
781
+ background: var(--color-hover-bg, rgba(0, 0, 0, 0.04));
782
+ }
783
+ .robot-accordion.expanded > .robot-accordion-header {
784
+ background: var(--color-active-bg, rgba(59, 130, 246, 0.08));
785
+ }
786
+ .robot-accordion-header img {
787
+ width: 32px;
788
+ height: 32px;
789
+ border-radius: 50%;
790
+ object-fit: cover;
791
+ flex-shrink: 0;
792
+ }
793
+ .robot-accordion-header > div {
794
+ display: flex;
795
+ flex-direction: column;
796
+ min-width: 0;
797
+ flex: 1;
798
+ overflow: hidden;
799
+ }
800
+ .robot-accordion-header span {
801
+ display: block;
802
+ font-size: 13px;
803
+ font-weight: 600;
804
+ max-width: 100%;
805
+ white-space: nowrap;
806
+ overflow: hidden;
807
+ text-overflow: ellipsis;
808
+ }
809
+ .robot-accordion-header small {
810
+ font-size: 11px;
811
+ opacity: 0.6;
812
+ }
813
+ .robot-accordion-header > svg {
814
+ opacity: 0.7;
815
+ flex-shrink: 0;
816
+ }
817
+ .robot-accordion-content {
818
+ padding: 2px 0 6px;
819
+ }
820
+ .empty-state {
821
+ display: flex;
822
+ align-items: center;
823
+ justify-content: center;
824
+ padding: 40px 0;
825
+ color: var(--color-font-2, #888);
826
+ opacity: 0.65;
827
+ }
828
+
829
+ .friend-list > div:first-child > div.base > span {
830
+ min-width: 0;
831
+ overflow: hidden;
832
+ white-space: nowrap;
833
+ text-overflow: clip;
834
+ -webkit-mask-image: linear-gradient(to right, black 0%, black 65%, transparent 100%);
835
+ mask-image: linear-gradient(to right, black 0%, black 65%, transparent 100%);
836
+ }
837
+
838
+ .friend-list-container {
839
+ overflow: hidden;
840
+ display: flex;
841
+ }
842
+
843
+ .onmsg-enter-active,
844
+ .onmsg-leave-active,
845
+ .onmsg-move {
846
+ transition: transform 0.4s;
847
+ }
848
+
849
+ .menu div.item > a {
850
+ font-size: 0.9rem !important;
851
+ }
852
+ .menu div.item > svg {
853
+ margin: 3px 10px 3px 0 !important;
854
+ font-size: 1rem !important;
855
+ }
856
+
857
+ .msg-menu-bg {
858
+ background: transparent !important;
859
+ }
860
+
861
+ @media (max-width: 700px) {
862
+ .friend-list-container {
863
+ overflow: unset;
864
+ }
865
+ .menu {
866
+ width: 140px !important;
867
+ }
868
+ }
869
+
870
+ @media (max-width: 500px) {
871
+ .friend-list-container {
872
+ overflow: hidden;
873
+ }
874
+ }
875
+ </style>