/** * wechat-importer.js — 将微信数据写入 GraphStore 知识图谱 * * 依赖 WechatReader(只读数据库)和 GraphStore(图谱写入)。 */ const { WechatReader } = require('./wechat-reader'); const { getGraphStore } = require('../../db/GraphStore'); class WechatImporter { constructor() { this.reader = new WechatReader(); } /** * 批量导入微信联系人到知识图谱 * - name = remark || nickName(优先使用备注名) * - wechatId = username * - analysisStatus = 'partial' * - 去重:按 wechatId 查找已有节点,执行 upsert * * @param {number} limit - 导入数量上限,默认 5000 * @returns {{ imported: number, skipped: number, total: number }} */ importContacts(limit = 5000) { const contacts = this.reader.getContacts(limit); const g = getGraphStore(); let imported = 0; let skipped = 0; const total = contacts.length; for (const c of contacts) { try { const name = c.remark || c.nickName; if (!name) { skipped++; continue; } // 按 wechatId 查找已有节点(避免重复) const existing = g.search(c.username).find( n => n._type === 'Person' && n.wechatId === c.username ); const nodeData = { ...(existing ? { id: existing.id } : {}), name, nickName: c.nickName || undefined, remark: c.remark || undefined, wechatId: c.username, alias: c.alias || undefined, notes: c.description || undefined, quanPin: c.quanPin || undefined, // 全拼排序键 pinYinInitial: c.pinYinInitial || undefined, // 拼音首字母 analysisStatus: 'partial', }; // 清理 undefined 字段 Object.keys(nodeData).forEach(k => { if (nodeData[k] === undefined) delete nodeData[k]; }); g.createNode('Person', nodeData); imported++; } catch (e) { console.warn(`[WechatImporter] 导入联系人失败 ${c.username}:`, e.message); skipped++; } } return { imported, skipped, total }; } /** * 导入指定联系人的全部聊天记录,upsert 到 Event 节点(按 wechatId 去重,重复导入只更新) * * @param {string} username - 微信 ID(wxid_xxx) * @param {number} limit - 消息条数上限,默认 99999(全部) * @returns {{ eventId: string|null, messages: number }} */ importChatHistory(username, limit = 99999) { const messages = this.reader.getMessages(username, limit); if (messages.length === 0) { return { eventId: null, messages: 0 }; } const g = getGraphStore(); // 最新 5 条作为摘要 const summaryLines = messages.slice(-5).map(m => { const sender = m.senderUsername || username; return `[${sender}] ${m.content.slice(0, 80)}`; }); // 最早和最新时间 const firstTime = messages[0]?.createTime || 0; const lastTime = messages[messages.length - 1]?.createTime || 0; function tsToDate(ts) { if (!ts) return undefined; return new Date(ts * 1000).toISOString().slice(0, 10); } // 按 wechatId 查找已有 Event,实现 upsert(避免重复导入) const existing = g.search(username).find( n => n._type === 'Event' && n.wechatId === username && n.type === 'chat' ); const event = g.createNode('Event', { ...(existing ? { id: existing.id } : {}), type: 'chat', summary: summaryLines.join('\n'), date: tsToDate(lastTime), rawText: messages.map(m => `[${m.senderUsername || username}] ${m.content}`).join('\n'), wechatId: username, msgCount: messages.length, startDate: tsToDate(firstTime), }); // 关联 Person(createEdge 内部已去重) const personNode = g.search(username).find( n => n._type === 'Person' && n.wechatId === username ); if (personNode) { g.createEdge(personNode.id, event.id, 'PARTICIPATED', { channel: 'wechat', msgCount: messages.length, }); } return { eventId: event.id, messages: messages.length }; } } module.exports = { WechatImporter };