@dcrays/dcgchat 0.4.27 → 0.5.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/index.js +292 -0
- package/openclaw.plugin.json +17 -1
- package/package.json +18 -13
- package/schemas/gateway-cron-finished.payload.json +39 -0
- package/index.ts +0 -26
- package/src/agent.ts +0 -128
- package/src/bot.ts +0 -500
- package/src/channel.ts +0 -470
- package/src/cron.ts +0 -194
- package/src/cronToolCall.ts +0 -202
- package/src/gateway/index.ts +0 -447
- package/src/gateway/security.ts +0 -95
- package/src/gateway/socket.ts +0 -285
- package/src/libs/ali-oss-6.23.0.tgz +0 -0
- package/src/libs/axios-1.13.6.tgz +0 -0
- package/src/libs/md5-2.3.0.tgz +0 -0
- package/src/libs/mime-types-3.0.2.tgz +0 -0
- package/src/libs/unzipper-0.12.3.tgz +0 -0
- package/src/libs/ws-8.19.0.tgz +0 -0
- package/src/monitor.ts +0 -165
- package/src/request/api.ts +0 -70
- package/src/request/oss.ts +0 -61
- package/src/request/request.ts +0 -192
- package/src/request/userInfo.ts +0 -99
- package/src/session.ts +0 -19
- package/src/sessionTermination.ts +0 -154
- package/src/skill.ts +0 -151
- package/src/tool.ts +0 -422
- package/src/tools/messageTool.ts +0 -224
- package/src/transport.ts +0 -203
- package/src/types.ts +0 -139
- package/src/utils/constant.ts +0 -7
- package/src/utils/gatewayMsgHanlder.ts +0 -55
- package/src/utils/global.ts +0 -160
- package/src/utils/log.ts +0 -15
- package/src/utils/params.ts +0 -88
- package/src/utils/searchFile.ts +0 -228
- package/src/utils/wsMessageHandler.ts +0 -64
- package/src/utils/zipExtract.ts +0 -97
- package/src/utils/zipPath.ts +0 -24
package/src/utils/searchFile.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { dcgLogger } from './log.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 从文本中提取 /mobook 目录下的文件
|
|
5
|
-
* @param {string} text
|
|
6
|
-
* @returns {string[]}
|
|
7
|
-
*/
|
|
8
|
-
const EXT_LIST = [
|
|
9
|
-
// 文档类
|
|
10
|
-
'doc',
|
|
11
|
-
'docx',
|
|
12
|
-
'xls',
|
|
13
|
-
'xlsx',
|
|
14
|
-
'ppt',
|
|
15
|
-
'pptx',
|
|
16
|
-
'pdf',
|
|
17
|
-
'txt',
|
|
18
|
-
'rtf',
|
|
19
|
-
'odt',
|
|
20
|
-
|
|
21
|
-
// 数据/开发
|
|
22
|
-
'json',
|
|
23
|
-
'xml',
|
|
24
|
-
'csv',
|
|
25
|
-
'yaml',
|
|
26
|
-
'yml',
|
|
27
|
-
|
|
28
|
-
// 前端/文本
|
|
29
|
-
'html',
|
|
30
|
-
'htm',
|
|
31
|
-
'md',
|
|
32
|
-
'markdown',
|
|
33
|
-
'css',
|
|
34
|
-
'js',
|
|
35
|
-
'ts',
|
|
36
|
-
|
|
37
|
-
// 图片
|
|
38
|
-
'png',
|
|
39
|
-
'jpg',
|
|
40
|
-
'jpeg',
|
|
41
|
-
'gif',
|
|
42
|
-
'bmp',
|
|
43
|
-
'webp',
|
|
44
|
-
'svg',
|
|
45
|
-
'ico',
|
|
46
|
-
'tiff',
|
|
47
|
-
|
|
48
|
-
// 音频
|
|
49
|
-
'mp3',
|
|
50
|
-
'wav',
|
|
51
|
-
'ogg',
|
|
52
|
-
'aac',
|
|
53
|
-
'flac',
|
|
54
|
-
'm4a',
|
|
55
|
-
|
|
56
|
-
// 视频
|
|
57
|
-
'mp4',
|
|
58
|
-
'avi',
|
|
59
|
-
'mov',
|
|
60
|
-
'wmv',
|
|
61
|
-
'flv',
|
|
62
|
-
'mkv',
|
|
63
|
-
'webm',
|
|
64
|
-
|
|
65
|
-
// 压缩包
|
|
66
|
-
'zip',
|
|
67
|
-
'rar',
|
|
68
|
-
'7z',
|
|
69
|
-
'tar',
|
|
70
|
-
'gz',
|
|
71
|
-
'bz2',
|
|
72
|
-
'xz',
|
|
73
|
-
|
|
74
|
-
// 可执行/程序
|
|
75
|
-
'exe',
|
|
76
|
-
'dmg',
|
|
77
|
-
'pkg',
|
|
78
|
-
'apk',
|
|
79
|
-
'ipa',
|
|
80
|
-
|
|
81
|
-
// 其他常见
|
|
82
|
-
'log',
|
|
83
|
-
'dat',
|
|
84
|
-
'bin'
|
|
85
|
-
]
|
|
86
|
-
/**
|
|
87
|
-
* 扩展名按长度降序,用于正则交替,避免 xls 抢先匹配 xlsx、htm 抢先匹配 html 等
|
|
88
|
-
*/
|
|
89
|
-
const EXT_SORTED_FOR_REGEX = [...EXT_LIST].sort((a, b) => b.length - a.length)
|
|
90
|
-
|
|
91
|
-
/** 去除控制符、零宽字符等常见脏值 */
|
|
92
|
-
function stripMobookNoise(s: string) {
|
|
93
|
-
return s.replace(/[\u0000-\u001F\u007F\u200B-\u200D\u200E\u200F\uFEFF]/g, '')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* 从文本中扫描 `.../mobook/...` 或 `...\mobook\...` 片段,按最长后缀匹配合法扩展名(兜底)
|
|
98
|
-
*/
|
|
99
|
-
function collectMobookPathsAfterNeedle(text: string, lower: string, needle: string, result: Set<string>): void {
|
|
100
|
-
let from = 0
|
|
101
|
-
while (from < text.length) {
|
|
102
|
-
const i = lower.indexOf(needle, from)
|
|
103
|
-
if (i < 0) break
|
|
104
|
-
const start = i + needle.length
|
|
105
|
-
const tail = text.slice(start)
|
|
106
|
-
const seg = tail.match(/^([^\s\]\)'"}\u3002,,]+)/)
|
|
107
|
-
if (!seg) {
|
|
108
|
-
from = start + 1
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
let raw = stripMobookNoise(seg[1]).trim()
|
|
112
|
-
if (!raw || raw.includes('\uFFFD')) {
|
|
113
|
-
from = start + 1
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
116
|
-
const low = raw.toLowerCase()
|
|
117
|
-
let matchedExt: string | undefined
|
|
118
|
-
for (const ext of EXT_SORTED_FOR_REGEX) {
|
|
119
|
-
if (low.endsWith(`.${ext}`)) {
|
|
120
|
-
matchedExt = ext
|
|
121
|
-
break
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (!matchedExt) {
|
|
125
|
-
from = start + 1
|
|
126
|
-
continue
|
|
127
|
-
}
|
|
128
|
-
const base = raw.slice(0, -(matchedExt.length + 1))
|
|
129
|
-
const fileName = `${base}.${matchedExt}`
|
|
130
|
-
if (isValidFileName(fileName)) {
|
|
131
|
-
result.add(normalizePath(`/mobook/${fileName}`))
|
|
132
|
-
}
|
|
133
|
-
from = start + 1
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function collectMobookPathsByScan(text: string, result: Set<string>): void {
|
|
138
|
-
const lower = text.toLowerCase()
|
|
139
|
-
collectMobookPathsAfterNeedle(text, lower, '/mobook/', result)
|
|
140
|
-
collectMobookPathsAfterNeedle(text, lower, '\\mobook\\', result)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function extractMobookFiles(text = '') {
|
|
144
|
-
if (typeof text !== 'string' || !text.trim()) return []
|
|
145
|
-
// 全角冒号(中文输入常见)→ 半角,便于匹配 c:\mobook\
|
|
146
|
-
text = text.replace(/\uFF1A/g, ':')
|
|
147
|
-
const result = new Set<string>()
|
|
148
|
-
// ✅ 扩展名(必须长扩展名优先,见 EXT_SORTED_FOR_REGEX)
|
|
149
|
-
const EXT = `(${EXT_SORTED_FOR_REGEX.join('|')})`
|
|
150
|
-
// ✅ 文件名字符(增强:支持中文、符号)
|
|
151
|
-
const FILE_NAME = `[\\w\\u4e00-\\u9fa5::《》()()\\-\\s]+?`
|
|
152
|
-
try {
|
|
153
|
-
// 1️⃣ `xxx.xxx`
|
|
154
|
-
const backtickReg = new RegExp(`\`([^\\\`]+?\\.${EXT})\``, 'gi')
|
|
155
|
-
;(text.match(backtickReg) || []).forEach((item) => {
|
|
156
|
-
const name = item.replace(/`/g, '').trim()
|
|
157
|
-
if (isValidFileName(name)) {
|
|
158
|
-
result.add(`/mobook/${name}`)
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
// 2️⃣ /mobook/xxx.xxx
|
|
162
|
-
const fullPathReg = new RegExp(`/mobook/${FILE_NAME}\\.${EXT}`, 'gi')
|
|
163
|
-
;(text.match(fullPathReg) || []).forEach((p) => {
|
|
164
|
-
result.add(normalizePath(p))
|
|
165
|
-
})
|
|
166
|
-
// 2️⃣b Windows 实际保存路径:C:\mobook\xxx、c:/mobook/xxx、\mobook\xxx(模型常写反斜杠,原先无法识别)
|
|
167
|
-
const winMobookReg = new RegExp(`(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]${FILE_NAME}\\.${EXT}`, 'gi')
|
|
168
|
-
;(text.match(winMobookReg) || []).forEach((full) => {
|
|
169
|
-
const name = full.replace(/^(?:[a-zA-Z]:)?[/\\\\]mobook[/\\\\]/i, '').trim()
|
|
170
|
-
if (isValidFileName(name)) {
|
|
171
|
-
result.add(normalizePath(`/mobook/${name}`))
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
// 3️⃣ mobook下的 xxx.xxx
|
|
175
|
-
const inlineReg = new RegExp(`mobook下的\\s*(${FILE_NAME}\\.${EXT})`, 'gi')
|
|
176
|
-
;(text.match(inlineReg) || []).forEach((item) => {
|
|
177
|
-
const match = item.match(new RegExp(`${FILE_NAME}\\.${EXT}`, 'i'))
|
|
178
|
-
if (match && isValidFileName(match[0])) {
|
|
179
|
-
result.add(`/mobook/${match[0].trim()}`)
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
// 🆕 4️⃣ **xxx.xxx**
|
|
183
|
-
const boldReg = new RegExp(`\\*\\*(${FILE_NAME}\\.${EXT})\\*\\*`, 'gi')
|
|
184
|
-
;(text.match(boldReg) || []).forEach((item) => {
|
|
185
|
-
const name = item.replace(/\*\*/g, '').trim()
|
|
186
|
-
if (isValidFileName(name)) {
|
|
187
|
-
result.add(`/mobook/${name}`)
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
// 🆕 5️⃣ xxx.xxx (123字节)
|
|
191
|
-
const looseReg = new RegExp(`(${FILE_NAME}\\.${EXT})\\s*\\(`, 'gi')
|
|
192
|
-
;(text.match(looseReg) || []).forEach((item) => {
|
|
193
|
-
const name = item.replace(/\s*\(.+$/, '').trim()
|
|
194
|
-
if (isValidFileName(name)) {
|
|
195
|
-
result.add(`/mobook/${name}`)
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
// 6️⃣ 兜底:绝对路径等 `.../mobook/<文件名>.<扩展名>` + 最长后缀匹配 + 去脏字符
|
|
199
|
-
collectMobookPathsByScan(text, result)
|
|
200
|
-
} catch (e) {
|
|
201
|
-
dcgLogger(`extractMobookFiles error:${e}`)
|
|
202
|
-
}
|
|
203
|
-
return [...result]
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 校验文件名是否合法(避免脏数据)
|
|
208
|
-
*/
|
|
209
|
-
function isValidFileName(name: string) {
|
|
210
|
-
if (!name) return false
|
|
211
|
-
const cleaned = stripMobookNoise(name).trim()
|
|
212
|
-
if (!cleaned) return false
|
|
213
|
-
if (cleaned.includes('\uFFFD')) return false
|
|
214
|
-
// 过滤异常字符
|
|
215
|
-
if (/[\/\\<>:"|?*]/.test(cleaned)) return false
|
|
216
|
-
// 长度限制(防止异常长字符串)
|
|
217
|
-
if (cleaned.length > 200) return false
|
|
218
|
-
return true
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 规范路径(去重用)
|
|
223
|
-
*/
|
|
224
|
-
function normalizePath(path: string) {
|
|
225
|
-
return path
|
|
226
|
-
.replace(/\/+/g, '/') // 多斜杠 → 单斜杠
|
|
227
|
-
.replace(/\/$/, '') // 去掉结尾 /
|
|
228
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { handleDcgchatMessage } from '../bot.js'
|
|
2
|
-
import { setMsgStatus, getSessionKey } from './global.js'
|
|
3
|
-
import type { InboundMessage } from '../types.js'
|
|
4
|
-
import { installSkill, uninstallSkill } from '../skill.js'
|
|
5
|
-
import { dcgLogger } from './log.js'
|
|
6
|
-
import { onDisabledCronJob, onEnabledCronJob, onRemoveCronJob, onRunCronJob } from '../cron.js'
|
|
7
|
-
import { ignoreToolCommand } from './constant.js'
|
|
8
|
-
import { createAgent } from '../agent.js'
|
|
9
|
-
|
|
10
|
-
export type ParsedWsPayload = {
|
|
11
|
-
messageType?: string
|
|
12
|
-
content: any
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 处理 WebSocket 已解析 JSON 且 content 已二次 parse 后的业务消息(openclaw_bot_chat / openclaw_bot_event)。
|
|
17
|
-
*/
|
|
18
|
-
export async function handleParsedWsMessage(parsed: ParsedWsPayload, rawPayload: string, accountId: string): Promise<void> {
|
|
19
|
-
if (parsed.messageType == 'openclaw_bot_chat') {
|
|
20
|
-
const msg = parsed as unknown as InboundMessage
|
|
21
|
-
// 与 monitor 原逻辑一致:工具类指令不进入 running,避免误触工具链监控
|
|
22
|
-
const effectiveSessionKey = getSessionKey(msg.content, accountId)
|
|
23
|
-
if (!ignoreToolCommand.includes(msg.content.text?.trim() ?? '')) {
|
|
24
|
-
setMsgStatus(effectiveSessionKey, 'running')
|
|
25
|
-
} else {
|
|
26
|
-
setMsgStatus(effectiveSessionKey, 'finished')
|
|
27
|
-
}
|
|
28
|
-
await handleDcgchatMessage(msg, accountId)
|
|
29
|
-
} else if (parsed.messageType == 'openclaw_bot_event') {
|
|
30
|
-
const { event_type, operation_type } = parsed.content ? parsed.content : ({} as Record<string, any>)
|
|
31
|
-
if (event_type === 'skill') {
|
|
32
|
-
const { skill_url, skill_code, skill_id, bot_token, websocket_trace_id } = parsed.content
|
|
33
|
-
const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id }
|
|
34
|
-
if (operation_type === 'install' || operation_type === 'enable' || operation_type === 'update') {
|
|
35
|
-
installSkill({ path: skill_url, code: skill_code }, content)
|
|
36
|
-
} else if (operation_type === 'remove' || operation_type === 'disable') {
|
|
37
|
-
uninstallSkill({ code: skill_code }, content)
|
|
38
|
-
} else {
|
|
39
|
-
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
40
|
-
}
|
|
41
|
-
} else if (event_type === 'agent') {
|
|
42
|
-
if (operation_type === 'create') {
|
|
43
|
-
await createAgent(parsed.content)
|
|
44
|
-
} else {
|
|
45
|
-
dcgLogger(`openclaw_bot_event unknown event_type: ${event_type}, ${rawPayload}`)
|
|
46
|
-
}
|
|
47
|
-
} else if (event_type === 'cron') {
|
|
48
|
-
const { job_id, message_id } = parsed.content
|
|
49
|
-
if (operation_type === 'remove') {
|
|
50
|
-
await onRemoveCronJob(job_id)
|
|
51
|
-
} else if (operation_type === 'enable') {
|
|
52
|
-
await onEnabledCronJob(job_id)
|
|
53
|
-
} else if (operation_type === 'disable') {
|
|
54
|
-
await onDisabledCronJob(job_id)
|
|
55
|
-
} else if (operation_type === 'run') {
|
|
56
|
-
await onRunCronJob(job_id, message_id)
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
dcgLogger(`openclaw_bot_event unknown operation_type: ${operation_type}, ${rawPayload}`)
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
dcgLogger(`ignoring unknown messageType: ${parsed.messageType}`, 'error')
|
|
63
|
-
}
|
|
64
|
-
}
|
package/src/utils/zipExtract.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
/** @ts-ignore */
|
|
4
|
-
import unzipper from 'unzipper'
|
|
5
|
-
import { pipeline } from 'stream/promises'
|
|
6
|
-
import { decodeZipEntryPath } from './zipPath.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 若且唯若所有条目都在同一顶层目录下(如 GitHub 下载的 repo-name/...),返回该目录名;否则返回 null。
|
|
10
|
-
* 不能再用「第一个多段路径的第一段」推断,否则 ZIP 条目顺序变化时会误判(例如先出现 .github/ 或 src/)。
|
|
11
|
-
*/
|
|
12
|
-
export function computeSharedZipRootPrefix(decodedPaths: string[]): string | null {
|
|
13
|
-
const normalized = decodedPaths
|
|
14
|
-
.map((p) => p.replace(/\\/g, '/').replace(/\/+$/, ''))
|
|
15
|
-
.filter((p) => p.length > 0)
|
|
16
|
-
|
|
17
|
-
if (normalized.length === 0) return null
|
|
18
|
-
|
|
19
|
-
const firstSegs = new Set<string>()
|
|
20
|
-
for (const p of normalized) {
|
|
21
|
-
const seg = p.split('/').filter(Boolean)[0]
|
|
22
|
-
if (seg) firstSegs.add(seg)
|
|
23
|
-
}
|
|
24
|
-
if (firstSegs.size !== 1) return null
|
|
25
|
-
|
|
26
|
-
const root = [...firstSegs][0]!
|
|
27
|
-
const prefix = `${root}/`
|
|
28
|
-
for (const p of normalized) {
|
|
29
|
-
if (p !== root && !p.startsWith(prefix)) return null
|
|
30
|
-
}
|
|
31
|
-
return root
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function assertSafeZipTarget(destDir: string, relativePath: string): string {
|
|
35
|
-
const resolvedPath = path.resolve(destDir, relativePath)
|
|
36
|
-
const resolvedDest = path.resolve(destDir)
|
|
37
|
-
const rel = path.relative(resolvedDest, resolvedPath)
|
|
38
|
-
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
39
|
-
throw new Error(`zip 路径越界: ${relativePath}`)
|
|
40
|
-
}
|
|
41
|
-
return resolvedPath
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type ZipEntry = {
|
|
45
|
-
path: string
|
|
46
|
-
pathBuffer: Buffer
|
|
47
|
-
flags: number
|
|
48
|
-
type: string
|
|
49
|
-
stream: (password?: string) => NodeJS.ReadableStream
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 将已下载的 zip 解压到 destDir;顶层单根目录(若存在)会被剥掉,与原先流式 Parse 行为一致,但根目录由全量路径计算,与条目顺序无关。
|
|
54
|
-
*/
|
|
55
|
-
export async function extractZipBufferToDirectory(buf: Buffer, destDir: string): Promise<void> {
|
|
56
|
-
const directory = await unzipper.Open.buffer(buf)
|
|
57
|
-
const files = (await directory.files) as ZipEntry[]
|
|
58
|
-
|
|
59
|
-
const decodedPaths = files.map((entry) =>
|
|
60
|
-
decodeZipEntryPath(entry.pathBuffer, entry.flags ?? 0, entry.path)
|
|
61
|
-
)
|
|
62
|
-
const rootDir = computeSharedZipRootPrefix(decodedPaths)
|
|
63
|
-
|
|
64
|
-
// 与 unzipper 默认 extract 一致:串行读各 entry,避免同一 buffer 上多路解压竞争
|
|
65
|
-
for (let i = 0; i < files.length; i++) {
|
|
66
|
-
const entry = files[i]!
|
|
67
|
-
const entryPath = decodedPaths[i]!
|
|
68
|
-
let newPath = entryPath.replace(/\\/g, '/')
|
|
69
|
-
if (rootDir) {
|
|
70
|
-
if (newPath === rootDir || newPath === `${rootDir}/`) {
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
if (newPath.startsWith(`${rootDir}/`)) {
|
|
74
|
-
newPath = newPath.slice(rootDir.length + 1)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
newPath = newPath.replace(/\/+$/, '')
|
|
78
|
-
if (!newPath) continue
|
|
79
|
-
|
|
80
|
-
const targetPath = assertSafeZipTarget(destDir, newPath)
|
|
81
|
-
|
|
82
|
-
if (entry.type === 'Directory') {
|
|
83
|
-
fs.mkdirSync(targetPath, { recursive: true })
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const parentDir = path.dirname(targetPath)
|
|
88
|
-
fs.mkdirSync(parentDir, { recursive: true })
|
|
89
|
-
const writeStream = fs.createWriteStream(targetPath)
|
|
90
|
-
try {
|
|
91
|
-
await pipeline(entry.stream(), writeStream)
|
|
92
|
-
} catch (err) {
|
|
93
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
94
|
-
throw new Error(`解压文件失败 ${entryPath}: ${message}`)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
package/src/utils/zipPath.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ZIP 文件名编码:规范要求 UTF-8 时设置 0x800;很多工具未设标志但仍写 UTF-8 字节。
|
|
3
|
-
* 无标志时若一律按 GBK 解码,会得到「鍥句功…」类乱码。先严格 UTF-8,失败再 GBK(兼容 Windows 中文 ZIP)。
|
|
4
|
-
*/
|
|
5
|
-
export function decodeZipEntryPath(
|
|
6
|
-
pathBuffer: Buffer | Uint8Array | undefined,
|
|
7
|
-
flags: number,
|
|
8
|
-
fallbackPath: string
|
|
9
|
-
): string {
|
|
10
|
-
if ((flags & 0x800) !== 0) {
|
|
11
|
-
if (pathBuffer) {
|
|
12
|
-
return new TextDecoder('utf-8').decode(pathBuffer)
|
|
13
|
-
}
|
|
14
|
-
return fallbackPath
|
|
15
|
-
}
|
|
16
|
-
if (pathBuffer && pathBuffer.length > 0) {
|
|
17
|
-
try {
|
|
18
|
-
return new TextDecoder('utf-8', { fatal: true }).decode(pathBuffer)
|
|
19
|
-
} catch {
|
|
20
|
-
return new TextDecoder('gbk').decode(pathBuffer)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return fallbackPath
|
|
24
|
-
}
|