koishi-plugin-chat-patch 5.6.1 → 6.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.
@@ -11,7 +11,7 @@
11
11
  <link rel="stylesheet" href="./bcui/css/style.css">
12
12
  <link rel="stylesheet" href="./bcui/css/color-light.css">
13
13
  <link rel="stylesheet" href="./css/append-light.css">
14
- <script type="module" crossorigin src="./assets/index-EJ2CcODr.js"></script>
14
+ <script type="module" crossorigin src="./assets/index--7-eCx0r.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="./assets/index-X9hFOAza.css">
16
16
  </head>
17
17
 
@@ -6,7 +6,6 @@ import {
6
6
  botKey,
7
7
  connectBackend,
8
8
  getActiveBot,
9
- getBlockedPlatforms,
10
9
  getBootstrap,
11
10
  getLogins,
12
11
  request,
@@ -38,6 +37,7 @@ import type { ConnectionHistoryItem, LoginCacheElem } from './elements/system'
38
37
  import type { UserFriendElem, UserGroupElem } from './elements/information'
39
38
 
40
39
  const HISTORY_KEY = 'chat-patch:connection-history'
40
+ const CLIENT_MESSAGE_CACHE_LIMIT = 500
41
41
  const logger = new Logger()
42
42
  const unsupportedMethods = new Set<string>()
43
43
 
@@ -45,14 +45,6 @@ function encodeKeyPart(value: string): string {
45
45
  return encodeURIComponent(value)
46
46
  }
47
47
 
48
- function isBlockedPlatform(platform: string): boolean {
49
- return getBlockedPlatforms().some((item) => {
50
- return item.exactMatch
51
- ? platform === item.platformName
52
- : platform.includes(item.platformName)
53
- })
54
- }
55
-
56
48
  function getChannelKind(value: unknown): 'group' | 'direct' | 'unknown' {
57
49
  if (typeof value === 'number') {
58
50
  if (value === 0) return 'group'
@@ -730,6 +722,9 @@ function recordBotMessage(platform: string, selfId: string, msg: Record<string,
730
722
  const messageId = String(msg.message_id ?? '')
731
723
  if (messageId && !messages.some((item) => String(item.message_id) === messageId)) {
732
724
  messages.push(msg)
725
+ if (messages.length > CLIENT_MESSAGE_CACHE_LIMIT) {
726
+ messages.splice(0, messages.length - CLIENT_MESSAGE_CACHE_LIMIT)
727
+ }
733
728
  chatStore.sessionMessageCache.set(cacheKey, messages)
734
729
  }
735
730
  const currentShowId = String(chatStore.chatInfo.show.id ?? '')
@@ -900,7 +895,6 @@ export function restoreBotStateFromMessageCache(platform: string, selfId: string
900
895
  }
901
896
 
902
897
  function onSatoriEvent(event: SatoriEvent) {
903
- if (isBlockedPlatform(event.platform)) return
904
898
  const oneBot = satoriEventToOneBot(event.body, String(event.platform || ''))
905
899
  if (oneBot) {
906
900
  oneBot._rawSatori = {
@@ -949,7 +943,6 @@ export function flushPendingBotEvents(platform: string, selfId: string) {
949
943
  if (!events?.length) return
950
944
  pendingBotEvents.delete(key)
951
945
  for (const event of events) {
952
- if (isBlockedPlatform(event.platform)) continue
953
946
  const oneBot = satoriEventToOneBot(event.body, String(event.platform || ''))
954
947
  if (oneBot) {
955
948
  oneBot._rawSatori = {
@@ -978,7 +971,7 @@ function onSatoriReady(logins: Array<{ platform: string; selfId: string; name: s
978
971
  login.creating = false
979
972
  login.status = true
980
973
  const authStore = useAuthStore()
981
- const visibleLogins = logins.filter((item) => !isBlockedPlatform(item.platform))
974
+ const visibleLogins = logins
982
975
  for (const item of visibleLogins) {
983
976
  if (!item.platform || !item.selfId) continue
984
977
  identityInfoCache.set(identityCacheKey('user', item.platform, item.selfId, item.selfId), {
@@ -57,17 +57,6 @@ function getObject(value: unknown): Record<string, unknown> {
57
57
  return typeof value === 'object' && value !== null ? value as Record<string, unknown> : {}
58
58
  }
59
59
 
60
- function isBlockedPlatform(
61
- platform: string,
62
- blockedPlatforms: SatoriBootstrap['blockedPlatforms'],
63
- ): boolean {
64
- return (blockedPlatforms ?? []).some((item) => {
65
- return item.exactMatch
66
- ? platform === item.platformName
67
- : platform.includes(item.platformName)
68
- })
69
- }
70
-
71
60
  async function parseJsonResponse(response: Response): Promise<unknown> {
72
61
  const text = await response.text()
73
62
  if (!response.ok) {
@@ -299,6 +288,7 @@ export function connect(
299
288
  let reconnectTimer = 0
300
289
  let retry = 0
301
290
  let disposed = false
291
+ let lastPongAt = 0
302
292
 
303
293
  const handleMessage = (event: MessageEvent) => {
304
294
  if (getString(event.data.source) === 'chat-patch-response') {
@@ -325,6 +315,7 @@ export function connect(
325
315
  socket = new WebSocket(wsUrl())
326
316
  socket.addEventListener('open', () => {
327
317
  retry = 0
318
+ lastPongAt = Date.now()
328
319
  socket?.send(JSON.stringify({
329
320
  op: 3,
330
321
  body: {
@@ -333,6 +324,10 @@ export function connect(
333
324
  },
334
325
  }))
335
326
  pingTimer = window.setInterval(() => {
327
+ if (Date.now() - lastPongAt > 30000) {
328
+ socket?.close()
329
+ return
330
+ }
336
331
  socket?.send(JSON.stringify({ op: 1, body: {} }))
337
332
  }, 10000)
338
333
  onStatus(true)
@@ -344,6 +339,7 @@ export function connect(
344
339
  const op = Number(raw.op)
345
340
  const body = getObject(raw.body)
346
341
  if (op === 4) {
342
+ lastPongAt = Date.now()
347
343
  const loginList = Array.isArray(body.logins) ? body.logins : []
348
344
  const seen = new Set<string>()
349
345
  const nextLogins: SatoriLogin[] = []
@@ -353,7 +349,7 @@ export function connect(
353
349
  const platform = getString(login.platform) || getString(user.platform)
354
350
  const selfId = getString(user.id) || getString(login.selfId)
355
351
  const key = [platform, selfId].map((value) => encodeURIComponent(value)).join(':')
356
- if (!platform || !selfId || seen.has(key) || isBlockedPlatform(platform, bootstrap?.blockedPlatforms ?? [])) continue
352
+ if (!platform || !selfId || seen.has(key)) continue
357
353
  seen.add(key)
358
354
  nextLogins.push({
359
355
  platform: getString(login.platform) || getString(user.platform),
@@ -371,8 +367,18 @@ export function connect(
371
367
  onReady(logins)
372
368
  return
373
369
  }
370
+ if (op === 1) {
371
+ lastPongAt = Date.now()
372
+ socket?.send(JSON.stringify({ op: 2, body: {} }))
373
+ return
374
+ }
375
+ if (op === 2) {
376
+ lastPongAt = Date.now()
377
+ return
378
+ }
374
379
  if (op === 5) return
375
380
  if (op === 0) {
381
+ lastPongAt = Date.now()
376
382
  sequence = Number(getObject(body).sn ?? sequence)
377
383
  localStorage.setItem('chat-patch:sn', String(sequence))
378
384
  onEvent({
@@ -53,6 +53,13 @@ function encodeKeyPart(value: unknown): string {
53
53
  return encodeURIComponent(String(value ?? ''))
54
54
  }
55
55
 
56
+ const HISTORY_INITIAL_LIMIT = 100
57
+ let historyRequestId = 0
58
+
59
+ function chatTargetKey(type: unknown, id: unknown, channelId: unknown): string {
60
+ return `${String(type ?? '')}:${String(id ?? '')}:${String(channelId ?? '')}`
61
+ }
62
+
56
63
  /**
57
64
  * 滚动到目标消息(不自动加载)
58
65
  * @param seqName DOM 名(chat-xx)
@@ -113,14 +120,26 @@ export async function loadHistory(info: BaseChatInfoElem) {
113
120
  const chatStore = useChatStore()
114
121
  const settingsStore = useSettingsStore()
115
122
  const uiStore = useUIStore()
123
+ const requestId = ++historyRequestId
124
+ const targetKey = chatTargetKey(info.type, info.id, info.channel_id)
125
+ const isStale = () => {
126
+ return requestId !== historyRequestId
127
+ || targetKey !== chatTargetKey(
128
+ chatStore.chatInfo.show.type,
129
+ chatStore.chatInfo.show.id,
130
+ chatStore.chatInfo.show.channel_id,
131
+ )
132
+ }
116
133
  uiStore.nowGetHistory = false
117
134
  uiStore.historyBeforeTime = undefined
135
+ uiStore.canLoadHistory = true
136
+ uiStore.loadHistoryFail = false
118
137
  chatStore.messageList = []
119
138
  const channelId = String(info.channel_id ?? info.id ?? '')
120
139
  const cacheKey = [authStore.loginInfo.platform, authStore.loginInfo.uin, channelId].map(encodeKeyPart).join(':')
121
- const cachedMessages = chatStore.sessionMessageCache.get(cacheKey)
122
- if (cachedMessages?.length) {
123
- chatStore.messageList = [...cachedMessages]
140
+ const sessionCached = chatStore.sessionMessageCache.get(cacheKey)
141
+ if (sessionCached?.length && !isStale()) {
142
+ chatStore.messageList = [...sessionCached]
124
143
  }
125
144
  // 本地有数据时立即显示,同时仍发网络请求以获取最新消息(避免遗漏)
126
145
  if (
@@ -132,17 +151,19 @@ export async function loadHistory(info: BaseChatInfoElem) {
132
151
  channelId,
133
152
  20,
134
153
  )
135
- if (localMsgs.length > 0) {
154
+ if (localMsgs.length > 0 && !isStale()) {
136
155
  chatStore.messageList = localMsgs
137
156
  }
138
157
  }
158
+ if (isStale()) return
139
159
  try {
140
160
  const cachedMessages = await loadChatHistoryFromCache({
141
161
  platform: String(authStore.loginInfo.platform ?? ''),
142
162
  selfId: String(authStore.loginInfo.uin ?? ''),
143
163
  channelId,
144
- limit: 500,
164
+ limit: HISTORY_INITIAL_LIMIT,
145
165
  })
166
+ if (isStale()) return
146
167
  if (cachedMessages.length > 0) {
147
168
  const mergedMap = new Map<string, any>()
148
169
  for (const msg of chatStore.messageList) {
@@ -167,7 +188,7 @@ export async function loadHistory(info: BaseChatInfoElem) {
167
188
  }
168
189
  return 0
169
190
  })
170
- chatStore.messageList = merged.slice(-500)
191
+ chatStore.messageList = merged.slice(-HISTORY_INITIAL_LIMIT)
171
192
  }
172
193
  } catch (error) {
173
194
  logger.error(error as Error, '[LocalHistory] 加载聊天记录缓存失败')
@@ -588,7 +588,6 @@ import { v4 as uuid } from 'uuid'
588
588
  import {
589
589
  scrollToMsg,
590
590
  downloadFile,
591
- loadHistory as loadHistoryFirst,
592
591
  shouldAutoFocus,
593
592
  vMenu,
594
593
  vMove,
@@ -612,7 +611,6 @@ import {
612
611
  import { Logger, LogType, PopInfo, PopType } from '../function/base'
613
612
  import { Connector, loadChatHistoryFromCache, saveSentSelfMessage, sendForwardMessage } from '../function/connect'
614
613
  import {
615
- BaseChatInfoElem,
616
614
  MsgItemElem,
617
615
  SQCodeElem,
618
616
  GroupMemberInfoElem,
@@ -735,6 +733,14 @@ const forwardList = ref(contactStore.userList)
735
733
  const chatImg = ref<any>(undefined)
736
734
  const trueLang = getTrueLang()
737
735
 
736
+ const HISTORY_PAGE_SIZE = 50
737
+ const HISTORY_INITIAL_LIMIT = 100
738
+
739
+ function currentChatTargetKey(): string {
740
+ const show = chatStore.chatInfo.show
741
+ return `${String(show.type ?? '')}:${String(show.id ?? '')}:${String(show.channel_id ?? '')}`
742
+ }
743
+
738
744
  function messageLocalTimeMs(item: any): number {
739
745
  const local = Number(item?.local_time ?? item?.timestamp_ms ?? item?.time_ms ?? 0)
740
746
  if (local) return local
@@ -745,6 +751,7 @@ function messageLocalTimeMs(item: any): number {
745
751
  async function refreshSelfHistory() {
746
752
  const id = chat.show.id
747
753
  if (!id || id === 0 || details.value[3].open || tags.value.showForwardPan) return
754
+ const targetKey = currentChatTargetKey()
748
755
  const channelId = chat.show.type === 'group'
749
756
  ? String(chat.show.channel_id ?? id)
750
757
  : String(chat.show.channel_id ?? id)
@@ -754,12 +761,12 @@ async function refreshSelfHistory() {
754
761
  platform: String(authStore.loginInfo.platform ?? ''),
755
762
  selfId: String(authStore.loginInfo.uin ?? ''),
756
763
  channelId,
757
- limit: 30,
764
+ limit: HISTORY_INITIAL_LIMIT,
758
765
  })
759
766
  } catch {
760
767
  return
761
768
  }
762
- if (!cached.length || String(chat.show.id) !== String(id)) return
769
+ if (!cached.length || String(chat.show.id) !== String(id) || targetKey !== currentChatTargetKey()) return
763
770
 
764
771
  const seen = new Set<string>()
765
772
  const addSeen = (item: any) => {
@@ -1178,6 +1185,8 @@ async function loadMoreHistory() {
1178
1185
  const firstMsg = list[0]
1179
1186
  const firstMsgId = firstMsg.message_id
1180
1187
  const firstMsgTime = Number(firstMsg?.time)
1188
+ const targetKey = currentChatTargetKey()
1189
+ const isStale = () => targetKey !== currentChatTargetKey()
1181
1190
  const cacheBeforeTime = Number(
1182
1191
  firstMsg?.local_time ?? firstMsg?.timestamp_ms ?? firstMsg?.time_ms ?? 0
1183
1192
  ) || (Number.isFinite(firstMsgTime) ? firstMsgTime * 1000 : 0)
@@ -1191,6 +1200,10 @@ async function loadMoreHistory() {
1191
1200
  uiStore.historyBeforeTime = undefined
1192
1201
  }
1193
1202
  uiStore.loadHistoryFail = false
1203
+ if (isStale()) {
1204
+ uiStore.nowGetHistory = false
1205
+ return
1206
+ }
1194
1207
 
1195
1208
  const channelId = String(
1196
1209
  chatStore.chatInfo.show.channel_id
@@ -1214,6 +1227,10 @@ async function loadMoreHistory() {
1214
1227
  20,
1215
1228
  )
1216
1229
  }
1230
+ if (isStale()) {
1231
+ uiStore.nowGetHistory = false
1232
+ return
1233
+ }
1217
1234
  if (localMsgs.length > 0) {
1218
1235
  const existingIds = new Set(chatStore.messageList.map((m) => String(m.message_id ?? '')))
1219
1236
  const addList = localMsgs.filter((m) => {
@@ -1230,14 +1247,22 @@ async function loadMoreHistory() {
1230
1247
  }
1231
1248
  }
1232
1249
  }
1250
+ if (isStale()) {
1251
+ uiStore.nowGetHistory = false
1252
+ return
1253
+ }
1233
1254
 
1234
1255
  const cached = await loadChatHistoryFromCache({
1235
1256
  platform: String(authStore.loginInfo.platform ?? ''),
1236
1257
  selfId: String(authStore.loginInfo.uin ?? ''),
1237
1258
  channelId,
1238
- limit: 20,
1259
+ limit: HISTORY_PAGE_SIZE,
1239
1260
  beforeTimeMs: cacheBeforeTime > 0 ? cacheBeforeTime : undefined,
1240
1261
  })
1262
+ if (isStale()) {
1263
+ uiStore.nowGetHistory = false
1264
+ return
1265
+ }
1241
1266
  const existingIds = new Set(chatStore.messageList.map((m) => String(m.message_id ?? '')))
1242
1267
  const addList = cached.filter((m) => {
1243
1268
  const msgId = String(m.message_id ?? '')
@@ -1246,7 +1271,7 @@ async function loadMoreHistory() {
1246
1271
  if (addList.length > 0) {
1247
1272
  chatStore.messageList.splice(0, 0, ...addList)
1248
1273
  }
1249
- if (cached.length < 20) {
1274
+ if (cached.length < HISTORY_PAGE_SIZE) {
1250
1275
  uiStore.canLoadHistory = false
1251
1276
  }
1252
1277
  uiStore.nowGetHistory = false
@@ -2938,23 +2963,6 @@ function updateList(newLength: number, oldLength: number) {
2938
2963
  NewMsgNum.value = Math.abs(newLength - oldLength)
2939
2964
  }
2940
2965
  }
2941
- if (
2942
- list.length > 200 &&
2943
- !uiStore.nowGetHistory &&
2944
- !tags.value.showBottomButton
2945
- ) {
2946
- chatStore.messageList = []
2947
- const info = {
2948
- type: chat.show.type,
2949
- id: chat.show.id,
2950
- name: chat.show.name,
2951
- avatar: chat.show.avatar,
2952
- jump: chat.show.jump,
2953
- } as BaseChatInfoElem
2954
- loadHistoryFirst(info)
2955
- uiStore.nowGetHistory = true
2956
- }
2957
-
2958
2966
  const pan = document.getElementById('msgPan')
2959
2967
  if (pan !== null) {
2960
2968
  const height = pan.scrollHeight
@@ -2968,7 +2976,7 @@ function updateList(newLength: number, oldLength: number) {
2968
2976
  )
2969
2977
  }
2970
2978
  if (!uiStore.nowGetHistory) {
2971
- if (!tags.value.showBottomButton) {
2979
+ if (shouldKeepChatAtBottom || oldLength <= 0) {
2972
2980
  scrollTo(newPan.scrollHeight)
2973
2981
  }
2974
2982
  if (oldLength <= 0) {
package/lib/database.d.ts CHANGED
@@ -11,8 +11,8 @@ export declare class ChatDatabase {
11
11
  constructor(ctx: Context, config: Config, logger: PluginLogger);
12
12
  private get db();
13
13
  initialize(): Promise<void>;
14
+ private loadLevelModule;
14
15
  dispose(): Promise<void>;
15
- private ensureOpen;
16
16
  private releaseShared;
17
17
  clearAll(): Promise<void>;
18
18
  appendMessage(record: MessageRecord): Promise<void>;
@@ -56,6 +56,8 @@ export declare class ChatDatabase {
56
56
  type: string;
57
57
  contacts: ContactCacheItem[];
58
58
  }>>;
59
- private trimMessages;
60
- private trimSelfMessages;
59
+ private trimChannel;
60
+ cleanupExcess(): Promise<void>;
61
+ private trimRows;
62
+ private extractRecordTime;
61
63
  }
package/lib/gateway.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Context } from 'koishi';
2
- import { Config } from './config';
3
2
  import { ChatDatabase } from './database';
4
3
  import { PluginLogger } from './logger';
5
4
  import { Recorder } from './recorder';
@@ -17,7 +16,6 @@ export type SatoriGatewayPayload = {
17
16
  type SatoriGatewayHandler = (payload: SatoriGatewayPayload) => void;
18
17
  export declare class SatoriGateway {
19
18
  private ctx;
20
- private config;
21
19
  private database;
22
20
  private recorder;
23
21
  private logger;
@@ -30,7 +28,8 @@ export declare class SatoriGateway {
30
28
  private sequence;
31
29
  private logins;
32
30
  private online;
33
- constructor(ctx: Context, config: Config, database: ChatDatabase, recorder: Recorder, logger: PluginLogger, onPayload: SatoriGatewayHandler);
31
+ private lastPongAt;
32
+ constructor(ctx: Context, database: ChatDatabase, recorder: Recorder, logger: PluginLogger, onPayload: SatoriGatewayHandler);
34
33
  start(): Promise<void>;
35
34
  dispose(): void;
36
35
  getLogins(): SatoriLoginInfo[];
@@ -40,7 +39,6 @@ export declare class SatoriGateway {
40
39
  private scheduleReconnect;
41
40
  private handleMessage;
42
41
  private normalizeLogins;
43
- private isBlocked;
44
42
  private setOnline;
45
43
  }
46
44
  export {};
package/lib/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { Context } from 'koishi';
2
+ import { Console } from '@koishijs/console';
3
+ import { Config } from './config';
4
+ export declare const name = "chat-patch";
5
+ export declare const reusable = false;
6
+ export declare const filter = false;
7
+ export declare const inject: {
8
+ required: string[];
9
+ };
10
+ declare module 'koishi' {
11
+ interface Context {
12
+ console: Console;
13
+ }
14
+ }
15
+ export declare const usage = "\n---\n\n\u57FA\u4E8E Satori \u534F\u8BAE\u7684 Koishi \u540E\u53F0\u804A\u5929\u5BA4\u3002\n\u9700\u8981\u5728 Koishi \u4E2D\u542F\u7528 server-satori\uFF0C\u5E76\u5728\u63D2\u4EF6\u5185\u6784\u5EFA web \u5E94\u7528\u3002\n\n---\n\n\u9700\u8981\u5B89\u88C5 w-node \u63D2\u4EF6\uFF0C\u5E76\u5F00\u542F w-node\u3001market\u3001server-satori \u63D2\u4EF6\u3002\n\n---\n";
16
+ export { Config } from './config';
17
+ export declare function apply(ctx: Context, config: Config): Promise<void>;