/** * Fix: ignore subject/主旨 lines when detecting forward shells & kinds pollution */ import fs from "fs"; { const path = "src/services/parse/instruction-lexicon.ts"; let src = fs.readFileSync(path, "utf8"); if (!src.includes("function stripSubjectLines")) { const insertAt = src.indexOf("export function isForwardShellSegment"); if (insertAt < 0) throw new Error("isForwardShellSegment missing"); const helper = `/** Drop subject/\\u4e3b\\u65e8 lines so thread titles do not count as body actions */\nfunction stripSubjectLines(text: string): string {\n return text\n .split(/\\n/)\n .filter((line) => !/(?:\\u4e3b\\u9898|\\u4e3b\\u65e8|Subject)\\s*[\\uff1a:]/i.test(line))\n .join("\\n");\n}\n\n`; // Use real Chinese in helper via unicode escapes already - wait use actual: const helper2 = `/** Drop subject lines so thread titles do not count as body actions */\nfunction stripSubjectLines(text: string): string {\n return text\n .split(/\\n/)\n .filter((line) => !/(?:\u4e3b\u9898|\u4e3b\u65e8|Subject)\\s*[\uFF1A:]/i.test(line))\n .join("\\n");\n}\n\n`; src = src.slice(0, insertAt) + helper2 + src.slice(insertAt); } // Rewrite isForwardShellSegment body const start = src.indexOf("export function isForwardShellSegment"); const end = src.indexOf("export function isNonInstructionSegment"); if (start < 0 || end < 0) throw new Error("shell funcs missing"); const replacement = `export function isForwardShellSegment(text: string): boolean { const compact = text.replace(/\\s+/g, " ").trim(); if (/\u53d1\u81ea\u6211\u7684iPhone/.test(compact)) return true; const bodyOnly = stripSubjectLines(text); const bodyCompact = bodyOnly.replace(/\\s+/g, " ").trim(); // date / QQ pure forward wrapper (keywords only in \u4e3b\u65e8) if ( /date@usasinogroup\\.com/i.test(compact) && !/Dear[,,]/.test(bodyOnly) && !BODY_ACTION_RE.test(bodyOnly) ) { return true; } if ( /\u53d1\u81ea\u6211\u7684iPhone|\u5927\u5c0f\\s*\\d/.test(compact) && !BODY_ACTION_RE.test(bodyOnly) && !/\u65b0\u589e\u9884\u62a5|\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5|\u65b0\u589e\u8f6c\u4ed3|DO\u8bf7\u67e5\u6536/.test( bodyOnly, ) ) { return true; } // short date forward: only headers + signature if ( /\u5bc4\u4ef6\u4eba/.test(compact) && /date@usasinogroup\\.com/i.test(compact) && bodyCompact.length < 120 && !/Dear[,,]/.test(bodyOnly) ) { return true; } return false; } `; src = src.slice(0, start) + replacement + src.slice(end); // Also update isNonInstructionSegment QQ shell to use stripSubjectLines src = src.replace( ` // QQ \u5916\u58f3\uff1a\u4ec5\u8f6c\u53d1\u5143\u6570\u636e\u3001\u65e0\u4e1a\u52a1\u52a8\u4f5c if ( /\u53d1\u81ea\u6211\u7684iPhone|\u5927\u5c0f\\s*\\d/.test(compact) && !BODY_ACTION_RE.test(compact) && !/\u65b0\u589e\u9884\u62a5|\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5|\u65b0\u589e\u8f6c\u4ed3|DO\u8bf7\u67e5\u6536/.test(compact) ) { return true; }`, ` // QQ shell without body actions (ignore subject title noise) { const bodyOnly = stripSubjectLines(text); if ( /\u53d1\u81ea\u6211\u7684iPhone|\u5927\u5c0f\\s*\\d/.test(compact) && !BODY_ACTION_RE.test(bodyOnly) && !/\u65b0\u589e\u9884\u62a5|\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5|\u65b0\u589e\u8f6c\u4ed3|DO\u8bf7\u67e5\u6536/.test( bodyOnly, ) ) { return true; } }`, ); fs.writeFileSync(path, src); console.log("lexicon shell ok"); } { const path = "src/services/parse/split-instructions.ts"; let src = fs.readFileSync(path, "utf8"); // Strengthen bodyForKindDetect: strip ALL subject lines + Re:新增预报 pollution lines const old = `function bodyForKindDetect(segBody: string): string { if (!BODY_ACTION_RE.test(segBody)) return segBody; return segBody .split(/\\n/) .filter( (line) => !/(?:\u4e3b\u9898|\u4e3b\u65e8|Subject)\\s*[\uFF1A:].*(?:\u65b0\u589e\u9884\u62a5|Fw:|\u8f6c\u53d1)/i.test(line), ) .join("\\n"); }`; const neu = `function bodyForKindDetect(segBody: string): string { // Always strip subject/Re title lines; they carry historical \u65b0\u589e\u9884\u62a5 const stripped = segBody .split(/\\n/) .filter((line) => { if (/(?:\u4e3b\u9898|\u4e3b\u65e8|Subject)\\s*[\uFF1A:]/i.test(line)) return false; if (/^Re:\\s*Re:/i.test(line.trim()) && /\u65b0\u589e\u9884\u62a5/.test(line)) return false; return true; }) .join("\\n"); return stripped; }`; if (!src.includes("function bodyForKindDetect")) throw new Error("no bodyForKindDetect"); // replace by function bounds const a = src.indexOf("function bodyForKindDetect"); const b = src.indexOf("export function detectKindsForSegment"); if (a < 0 || b < 0) throw new Error("bounds"); src = src.slice(0, a) + neu + "\n\n" + src.slice(b); // useSubject: only when body (stripped) has no action words src = src.replace( ` const useSubject = !BODY_ACTION_RE.test(segBody) || /\\u8bf7\\u67e5\\u6536\\u65b0\\u589e\\u9884\\u62a5/.test(bodyText);`, ` const useSubject = !BODY_ACTION_RE.test(bodyText) || /\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5/.test(bodyText);`, ); // Also fix the actual Chinese version if unicode escape didn't match src = src.replace( /const useSubject =\s*!BODY_ACTION_RE\.test\(segBody\) \|\|\s*\/请查收新增预报\/\.test\(bodyText\);/, `const useSubject = !BODY_ACTION_RE.test(bodyText) || /\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5/.test(bodyText);`, ); fs.writeFileSync(path, src); console.log("split bodyForKindDetect ok"); }