mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(parser): verify QQ manifest fallbacks
Require the chunked marker before a manifest filename can override normal format detection.
This commit is contained in:
1 parent
9847e30855
commit
faedaa852a
4 files changed
+36
-2
No files matched your search
@@ -51,6 +51,7 @@ export const feature: FormatFeature = {
|
||||
// V5 使用 metadata.format,0.1.x 将格式标识移至 chunked.format。
|
||||
head: [/"format"\s*:\s*"chunked-jsonl"/, /"chunked"\s*:\s*\{[^{}]*"format"\s*:\s*"jsonl"/],
|
||||
filename: [/^manifest\.json$/i],
|
||||
filenameFallbackPatterns: [/"chunked"\s*:\s*\{[\s\S]*?"format"\s*:\s*"jsonl"/],
|
||||
requiredFields: ['metadata', 'chatInfo'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ function systemMessage(text: string): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
async function parseContent(content: string): Promise<ParseResult> {
|
||||
async function parseContent(content: string, filename = 'qce-export.json'): Promise<ParseResult> {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'chatlab-qce-parser-'))
|
||||
try {
|
||||
const filePath = join(dir, 'qce-export.json')
|
||||
const filePath = join(dir, filename)
|
||||
writeFileSync(filePath, content, 'utf-8')
|
||||
assert.equal(detectFormat(filePath)?.id, 'shuakami-qq-exporter')
|
||||
return await parseFileSync(filePath)
|
||||
@@ -70,6 +70,19 @@ async function parseContent(content: string): Promise<ParseResult> {
|
||||
}
|
||||
|
||||
describe('shuakami-qq-exporter parser', () => {
|
||||
it('does not misclassify a single-file export renamed to manifest.json', async () => {
|
||||
const result = await parseContent(
|
||||
makeExport({
|
||||
chatInfoType: 'group',
|
||||
senders: [{ uid: 'u_100', name: 'Alice' }],
|
||||
messages: [textMessage('100', 'Alice', 'hello')],
|
||||
}),
|
||||
'manifest.json'
|
||||
)
|
||||
|
||||
assert.equal(result.messages.length, 1)
|
||||
})
|
||||
|
||||
it('prefers chatInfo.type and skips placeholder system senders', async () => {
|
||||
const content = makeExport({
|
||||
chatInfoType: 'private',
|
||||
|
||||
@@ -43,6 +43,18 @@ function matchFilenameSignatures(filePath: string, patterns: RegExp[]): boolean
|
||||
return patterns.some((pattern) => pattern.test(filename))
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件名只能作为补充特征;需要全文确认时再读取完整文件。
|
||||
*/
|
||||
function matchFilenameFallbackPatterns(filePath: string, patterns: RegExp[]): boolean {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8')
|
||||
return patterns.every((pattern) => pattern.test(content))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查必需字段是否存在
|
||||
*/
|
||||
@@ -202,6 +214,12 @@ export class FormatSniffer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!headMatch && filenameMatch && signatures.filenameFallbackPatterns) {
|
||||
if (!filePath || !matchFilenameFallbackPatterns(filePath, signatures.filenameFallbackPatterns)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查必需字段(如果定义了)
|
||||
if (signatures.requiredFields && signatures.requiredFields.length > 0) {
|
||||
if (!matchRequiredFields(headContent, signatures.requiredFields)) {
|
||||
|
||||
@@ -70,6 +70,8 @@ export interface FormatSignatures {
|
||||
head?: RegExp[]
|
||||
/** 文件名正则匹配(任意一个匹配即可,作为文件头匹配的补充) */
|
||||
filename?: RegExp[]
|
||||
/** 文件名补充匹配时,必须在完整文件中命中的模式 */
|
||||
filenameFallbackPatterns?: RegExp[]
|
||||
/** 必须存在的 JSON 字段路径 */
|
||||
requiredFields?: string[]
|
||||
/** 字段值模式匹配 */
|
||||
|
||||
Reference in new issue
Block a user