wechat-importer.js
4.8 KB
1
2
3
4
5
6
7
8
9
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
131
132
133
134
135
136
/**
* 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 };