koishi-plugin-chat-patch 2.1.2 → 2.3.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/icons/activity.vue +9 -9
- package/client/icons/index.ts +4 -4
- package/client/index.scss +90 -90
- package/client/index.ts +25 -25
- package/client/vue/chat-logic.ts +683 -678
- package/client/vue/composables/useChatActions.ts +1 -1
- package/client/vue/composables/useChatData.ts +1 -1
- package/client/vue/composables/useImageCache.ts +1 -1
- package/client/vue/composables/useVideoCache.ts +130 -0
- package/client/vue/index.vue +658 -612
- package/client/vue/types.ts +1 -1
- package/dist/index.js +2 -2
- package/lib/api-handlers.d.ts +1 -0
- package/lib/file-manager.d.ts +25 -0
- package/lib/index.js +362 -200
- package/lib/message-handler.d.ts +7 -5
- package/lib/utils.d.ts +13 -0
- package/package.json +52 -52
- package/readme.md +25 -25
- package/src/api-handlers.ts +704 -704
- package/src/config.ts +45 -46
- package/src/file-manager.ts +215 -169
- package/src/index.ts +175 -125
- package/src/message-handler.ts +440 -407
- package/src/types.ts +62 -62
- package/src/utils.ts +151 -144
package/src/types.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { h } from 'koishi'
|
|
2
|
-
|
|
3
|
-
export interface BotInfo {
|
|
4
|
-
selfId: string
|
|
5
|
-
platform: string
|
|
6
|
-
username: string
|
|
7
|
-
avatar?: string
|
|
8
|
-
status: 'online' | 'offline'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ChannelInfo {
|
|
12
|
-
id: string
|
|
13
|
-
name: string
|
|
14
|
-
type: number | string
|
|
15
|
-
channelId?: string
|
|
16
|
-
guildName?: string
|
|
17
|
-
isDirect?: boolean
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface QuoteInfo {
|
|
21
|
-
messageId: string
|
|
22
|
-
id: string
|
|
23
|
-
content: string
|
|
24
|
-
elements?: h[]
|
|
25
|
-
user: {
|
|
26
|
-
id: string
|
|
27
|
-
name: string
|
|
28
|
-
userId: string
|
|
29
|
-
avatar?: string
|
|
30
|
-
username: string
|
|
31
|
-
}
|
|
32
|
-
timestamp: number
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface MessageInfo {
|
|
36
|
-
id: string
|
|
37
|
-
content: string
|
|
38
|
-
userId: string
|
|
39
|
-
username: string
|
|
40
|
-
avatar?: string
|
|
41
|
-
timestamp: number
|
|
42
|
-
channelId: string
|
|
43
|
-
selfId: string
|
|
44
|
-
elements?: h[]
|
|
45
|
-
type: 'user' | 'bot'
|
|
46
|
-
guildId?: string
|
|
47
|
-
guildName?: string
|
|
48
|
-
platform: string
|
|
49
|
-
quote?: QuoteInfo
|
|
50
|
-
isDirect?: boolean
|
|
51
|
-
sending?: boolean
|
|
52
|
-
realId?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface ChatData {
|
|
56
|
-
bots: Record<string, BotInfo>
|
|
57
|
-
channels: Record<string, Record<string, ChannelInfo>>
|
|
58
|
-
messages: Record<string, MessageInfo[]>
|
|
59
|
-
pinnedBots: string[]
|
|
60
|
-
pinnedChannels: string[]
|
|
61
|
-
lastSaveTime?: number
|
|
62
|
-
}
|
|
1
|
+
import { h } from 'koishi'
|
|
2
|
+
|
|
3
|
+
export interface BotInfo {
|
|
4
|
+
selfId: string
|
|
5
|
+
platform: string
|
|
6
|
+
username: string
|
|
7
|
+
avatar?: string
|
|
8
|
+
status: 'online' | 'offline'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ChannelInfo {
|
|
12
|
+
id: string
|
|
13
|
+
name: string
|
|
14
|
+
type: number | string
|
|
15
|
+
channelId?: string
|
|
16
|
+
guildName?: string
|
|
17
|
+
isDirect?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface QuoteInfo {
|
|
21
|
+
messageId: string
|
|
22
|
+
id: string
|
|
23
|
+
content: string
|
|
24
|
+
elements?: h[]
|
|
25
|
+
user: {
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
userId: string
|
|
29
|
+
avatar?: string
|
|
30
|
+
username: string
|
|
31
|
+
}
|
|
32
|
+
timestamp: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface MessageInfo {
|
|
36
|
+
id: string
|
|
37
|
+
content: string
|
|
38
|
+
userId: string
|
|
39
|
+
username: string
|
|
40
|
+
avatar?: string
|
|
41
|
+
timestamp: number
|
|
42
|
+
channelId: string
|
|
43
|
+
selfId: string
|
|
44
|
+
elements?: h[]
|
|
45
|
+
type: 'user' | 'bot'
|
|
46
|
+
guildId?: string
|
|
47
|
+
guildName?: string
|
|
48
|
+
platform: string
|
|
49
|
+
quote?: QuoteInfo
|
|
50
|
+
isDirect?: boolean
|
|
51
|
+
sending?: boolean
|
|
52
|
+
realId?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ChatData {
|
|
56
|
+
bots: Record<string, BotInfo>
|
|
57
|
+
channels: Record<string, Record<string, ChannelInfo>>
|
|
58
|
+
messages: Record<string, MessageInfo[]>
|
|
59
|
+
pinnedBots: string[]
|
|
60
|
+
pinnedChannels: string[]
|
|
61
|
+
lastSaveTime?: number
|
|
62
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,144 +1,151 @@
|
|
|
1
|
-
import { Config } from './config'
|
|
2
|
-
import { Context } from 'koishi'
|
|
3
|
-
import { writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { createHash } from 'node:crypto'
|
|
6
|
-
import { pathToFileURL } from 'node:url'
|
|
7
|
-
|
|
8
|
-
export class Utils {
|
|
9
|
-
constructor(private config: Config, private ctx?: Context) { }
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (element.type === '
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (str.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
key === '
|
|
131
|
-
key === '
|
|
132
|
-
key === '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
1
|
+
import { Config } from './config'
|
|
2
|
+
import { Context } from 'koishi'
|
|
3
|
+
import { writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
export class Utils {
|
|
9
|
+
constructor(private config: Config, private ctx?: Context) { }
|
|
10
|
+
|
|
11
|
+
isPlatformBlocked(platform: string): boolean {
|
|
12
|
+
if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const blockedPlatform of this.config.blockedPlatforms) {
|
|
17
|
+
if (blockedPlatform.exactMatch) {
|
|
18
|
+
if (platform === blockedPlatform.platformName) {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
if (platform.includes(blockedPlatform.platformName)) {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
extractTextContent(elements: any[]): string {
|
|
32
|
+
let text = ''
|
|
33
|
+
for (const element of elements) {
|
|
34
|
+
if (element.type === 'text') {
|
|
35
|
+
text += element.attrs?.content || ''
|
|
36
|
+
} else if (element.type === 'p') {
|
|
37
|
+
if (element.children && element.children.length > 0) {
|
|
38
|
+
text += this.extractTextContent(element.children) + '\n'
|
|
39
|
+
}
|
|
40
|
+
} else if (element.children && element.children.length > 0) {
|
|
41
|
+
text += this.extractTextContent(element.children)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return text
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private isBase64(str: string): boolean {
|
|
48
|
+
if (!str || typeof str !== 'string') return false
|
|
49
|
+
|
|
50
|
+
if (str.startsWith('data:')) {
|
|
51
|
+
return str.includes('base64,')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (str.length > 100) {
|
|
55
|
+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/
|
|
56
|
+
return base64Regex.test(str)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
persistBase64Image(base64Data: string): string {
|
|
63
|
+
if (!this.ctx || !base64Data.startsWith('data:image/')) return base64Data
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const dir = join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-images')
|
|
67
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
68
|
+
|
|
69
|
+
const hash = createHash('md5').update(base64Data).digest('hex')
|
|
70
|
+
const ext = base64Data.split(';')[0].split('/')[1] || 'png'
|
|
71
|
+
const filename = `${Date.now()}_${hash}.${ext}`
|
|
72
|
+
const filePath = join(dir, filename)
|
|
73
|
+
|
|
74
|
+
const base64Content = base64Data.split(',')[1]
|
|
75
|
+
writeFileSync(filePath, Buffer.from(base64Content, 'base64'))
|
|
76
|
+
|
|
77
|
+
this.cleanupPersistImages(dir)
|
|
78
|
+
|
|
79
|
+
return pathToFileURL(filePath).href
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return base64Data
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private cleanupPersistImages(dir: string) {
|
|
86
|
+
try {
|
|
87
|
+
const files = readdirSync(dir)
|
|
88
|
+
.map(name => ({ name, path: join(dir, name), mtime: statSync(join(dir, name)).mtimeMs }))
|
|
89
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
90
|
+
|
|
91
|
+
if (files.length > this.config.maxPersistImages) {
|
|
92
|
+
files.slice(this.config.maxPersistImages).forEach(f => unlinkSync(f.path))
|
|
93
|
+
}
|
|
94
|
+
} catch (e) { }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cleanBase64Content(obj: any, isBotMessage: boolean = false): any {
|
|
98
|
+
if (obj === null || obj === undefined) {
|
|
99
|
+
return obj
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof obj === 'string') {
|
|
103
|
+
if (this.isBase64(obj)) {
|
|
104
|
+
|
|
105
|
+
if (isBotMessage && !obj.startsWith('data:image/')) {
|
|
106
|
+
return '[富媒体内容已省略]'
|
|
107
|
+
}
|
|
108
|
+
return this.persistBase64Image(obj)
|
|
109
|
+
}
|
|
110
|
+
return obj
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Array.isArray(obj)) {
|
|
114
|
+
return obj.map(item => this.cleanBase64Content(item, isBotMessage))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof obj === 'object') {
|
|
118
|
+
const cleaned: any = {}
|
|
119
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
120
|
+
|
|
121
|
+
if (isBotMessage && obj.type && !['text', 'image', 'img'].includes(obj.type)) {
|
|
122
|
+
|
|
123
|
+
if (typeof value === 'string' && (key === 'src' || key === 'url' || key === 'file') && this.isBase64(value)) {
|
|
124
|
+
cleaned[key] = '[富媒体内容已省略]'
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof value === 'string' && (
|
|
130
|
+
key === 'src' ||
|
|
131
|
+
key === 'url' ||
|
|
132
|
+
key === 'file' ||
|
|
133
|
+
key === 'data' ||
|
|
134
|
+
key === 'content'
|
|
135
|
+
) && this.isBase64(value)) {
|
|
136
|
+
|
|
137
|
+
if (isBotMessage && !value.startsWith('data:image/')) {
|
|
138
|
+
cleaned[key] = '[富媒体内容已省略]'
|
|
139
|
+
} else {
|
|
140
|
+
cleaned[key] = this.persistBase64Image(value)
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
cleaned[key] = this.cleanBase64Content(value, isBotMessage)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return cleaned
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return obj
|
|
150
|
+
}
|
|
151
|
+
}
|