@chainlesschain/personal-data-hub 0.4.7 → 0.4.23
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/__tests__/adapters/biz-tianyancha.test.js +159 -0
- package/__tests__/adapters/doc-baidu-netdisk.test.js +102 -0
- package/__tests__/adapters/doc-camscanner.test.js +147 -0
- package/__tests__/adapters/doc-platforms.test.js +177 -0
- package/__tests__/adapters/gov-ixiamen.test.js +150 -0
- package/__tests__/adapters/gov-tax.test.js +135 -0
- package/__tests__/adapters/health-meiyou.test.js +125 -0
- package/__tests__/adapters/music-kugou.test.js +187 -0
- package/__tests__/adapters/recruit-boss.test.js +180 -0
- package/__tests__/adapters/shopping-dianping.test.js +239 -0
- package/__tests__/adapters/social-csdn.test.js +175 -0
- package/__tests__/adapters/social-dongchedi.test.js +165 -0
- package/__tests__/adapters/social-zhihu.test.js +246 -0
- package/__tests__/adapters/travel-ctrip.test.js +175 -1
- package/__tests__/adapters/travel-didi.test.js +204 -0
- package/__tests__/adapters/travel-tongcheng.test.js +289 -0
- package/__tests__/adapters/video-platforms.test.js +152 -0
- package/__tests__/adapters/video-xigua.test.js +106 -0
- package/__tests__/adapters/wework-pc.test.js +124 -0
- package/lib/adapter-guide.js +25 -3
- package/lib/adapters/_document-base.js +370 -0
- package/lib/adapters/_video-base.js +331 -0
- package/lib/adapters/biz-tianyancha/index.js +348 -0
- package/lib/adapters/doc-baidu-netdisk/index.js +91 -0
- package/lib/adapters/doc-camscanner/index.js +102 -0
- package/lib/adapters/doc-tencent-docs/index.js +94 -0
- package/lib/adapters/doc-wps/index.js +77 -0
- package/lib/adapters/gov-ixiamen/index.js +380 -0
- package/lib/adapters/gov-tax/index.js +451 -0
- package/lib/adapters/health-meiyou/index.js +393 -0
- package/lib/adapters/music-kugou/index.js +418 -0
- package/lib/adapters/recruit-boss/index.js +442 -0
- package/lib/adapters/shopping-dianping/index.js +473 -0
- package/lib/adapters/social-csdn/index.js +444 -0
- package/lib/adapters/social-dongchedi/index.js +360 -0
- package/lib/adapters/social-zhihu/index.js +488 -0
- package/lib/adapters/travel-ctrip/index.js +255 -40
- package/lib/adapters/travel-didi/index.js +327 -0
- package/lib/adapters/travel-tongcheng/index.js +393 -0
- package/lib/adapters/video-iqiyi/index.js +75 -0
- package/lib/adapters/video-tencent/index.js +78 -0
- package/lib/adapters/video-xigua/index.js +68 -0
- package/lib/adapters/wework-pc/index.js +31 -0
- package/lib/index.js +40 -0
- package/package.json +1 -1
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §A10 — BOSS 直聘 (com.hpbr.bosszhipin) recruitment adapter, dual-mode
|
|
3
|
+
* (snapshot + cookie-api). Phase 13+ §12.1 "沟通职位 + 简历" (ROI ⭐⭐⭐).
|
|
4
|
+
*
|
|
5
|
+
* BOSS 直聘's personal data is the user's job-search activity: positions they
|
|
6
|
+
* chatted with recruiters about, and jobs they applied to. Both map to
|
|
7
|
+
* INTERACTION events (the user reaching out to / engaging a company), with the
|
|
8
|
+
* recruiter as a contact Person. Mirrors social-zhihu's two-mode shape but uses
|
|
9
|
+
* a recruitment-specific normalize.
|
|
10
|
+
*
|
|
11
|
+
* 1. snapshot mode (opts.inputPath): JSON schemaVersion 1, stateless.
|
|
12
|
+
* 2. cookie-api mode (opts.account.cookies): fetch the logged-in user's chat
|
|
13
|
+
* list + delivery (application) list from zhipin.com via the injected
|
|
14
|
+
* `fetchFn` (Android in-APK cc → OkHttp; desktop hub → Electron WebView net
|
|
15
|
+
* request), paginate, normalize. A sign seam (opts.signProvider) covers
|
|
16
|
+
* BOSS's `__zp_stoken__` / anti-bot token; best-effort unsigned when absent.
|
|
17
|
+
* Endpoints overridable via opts.chatsUrl / opts.deliveriesUrl (best-effort,
|
|
18
|
+
* not field-verified — FAMILY-23 playbook).
|
|
19
|
+
*
|
|
20
|
+
* Snapshot schema (schemaVersion 1):
|
|
21
|
+
* {
|
|
22
|
+
* "schemaVersion": 1, "snapshottedAt": <ms>,
|
|
23
|
+
* "account": { "userId": "...", "name": "..." },
|
|
24
|
+
* "events": [
|
|
25
|
+
* { "kind": "chat", "id": "chat-<id>", "jobId": "...", "jobTitle": "...",
|
|
26
|
+
* "company": "...", "hrName": "...", "hrId": "...", "salary": "...",
|
|
27
|
+
* "city": "...", "lastChatTime": <s|ms> },
|
|
28
|
+
* { "kind": "application", "id": "apply-<id>", "jobId": "...", "jobTitle": "...",
|
|
29
|
+
* "company": "...", "status": "...", "deliverTime": <s|ms> }
|
|
30
|
+
* ]
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
const fs = require("node:fs");
|
|
37
|
+
const { newId } = require("../../ids");
|
|
38
|
+
const {
|
|
39
|
+
ENTITY_TYPES,
|
|
40
|
+
PERSON_SUBTYPES,
|
|
41
|
+
EVENT_SUBTYPES,
|
|
42
|
+
CAPTURED_BY,
|
|
43
|
+
} = require("../../constants");
|
|
44
|
+
const { CookieAuth } = require("../shopping-base");
|
|
45
|
+
|
|
46
|
+
const NAME = "recruit-boss";
|
|
47
|
+
const VERSION = "0.1.0";
|
|
48
|
+
const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
49
|
+
|
|
50
|
+
const KIND_CHAT = "chat";
|
|
51
|
+
const KIND_APPLICATION = "application";
|
|
52
|
+
const VALID_SNAPSHOT_KINDS = Object.freeze([KIND_CHAT, KIND_APPLICATION]);
|
|
53
|
+
|
|
54
|
+
const CHATS_URL = "https://www.zhipin.com/wapi/zpchat/geek/contactList";
|
|
55
|
+
const DELIVERIES_URL = "https://www.zhipin.com/wapi/zpgeek/mobile/geek/deliver/list.json";
|
|
56
|
+
const PAGE_SIZE = 20;
|
|
57
|
+
|
|
58
|
+
function parseTime(v) {
|
|
59
|
+
if (Number.isFinite(v)) return v > 1e12 ? v : v >= 1e9 ? v * 1000 : v;
|
|
60
|
+
if (typeof v === "string") {
|
|
61
|
+
if (/^\d+$/.test(v)) {
|
|
62
|
+
const n = parseInt(v, 10);
|
|
63
|
+
return n > 1e12 ? n : n >= 1e9 ? n * 1000 : n;
|
|
64
|
+
}
|
|
65
|
+
const t = Date.parse(v);
|
|
66
|
+
return Number.isFinite(t) ? t : null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function stableOriginalId(kind, id) {
|
|
72
|
+
const safe =
|
|
73
|
+
(typeof id === "string" && id.length > 0 && id) ||
|
|
74
|
+
(typeof id === "number" && Number.isFinite(id) && String(id)) ||
|
|
75
|
+
`unknown-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
76
|
+
return `boss:${kind}:${safe}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class BossZhipinAdapter {
|
|
80
|
+
constructor(opts = {}) {
|
|
81
|
+
this.account = opts.account || null;
|
|
82
|
+
this._cookieAuth =
|
|
83
|
+
opts.account && opts.account.cookies
|
|
84
|
+
? new CookieAuth({ platform: "boss", cookies: opts.account.cookies })
|
|
85
|
+
: null;
|
|
86
|
+
this._fetchFn = typeof opts.fetchFn === "function" ? opts.fetchFn : defaultFetch;
|
|
87
|
+
this._signProvider =
|
|
88
|
+
typeof opts.signProvider === "function" ? opts.signProvider : null;
|
|
89
|
+
this._urls = {
|
|
90
|
+
chats: opts.chatsUrl || CHATS_URL,
|
|
91
|
+
deliveries: opts.deliveriesUrl || DELIVERIES_URL,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.name = NAME;
|
|
95
|
+
this.version = VERSION;
|
|
96
|
+
this.capabilities = [
|
|
97
|
+
"sync:snapshot",
|
|
98
|
+
"sync:cookie-api",
|
|
99
|
+
"parse:boss-chat",
|
|
100
|
+
"parse:boss-application",
|
|
101
|
+
];
|
|
102
|
+
this.extractMode = "web-api";
|
|
103
|
+
this.rateLimits = { perMinute: 8, perDay: 200 };
|
|
104
|
+
this.dataDisclosure = {
|
|
105
|
+
fields: [
|
|
106
|
+
"boss:chat (jobTitle / company / hrName / salary / city)",
|
|
107
|
+
"boss:application (jobTitle / company / status / deliverTime)",
|
|
108
|
+
],
|
|
109
|
+
sensitivity: "medium",
|
|
110
|
+
legalGate: false,
|
|
111
|
+
defaultInclude: { chat: true, application: true },
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this._deps = { fs };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async authenticate(ctx = {}) {
|
|
118
|
+
if (ctx && typeof ctx.inputPath === "string" && ctx.inputPath.length > 0) {
|
|
119
|
+
try {
|
|
120
|
+
this._deps.fs.accessSync(ctx.inputPath, this._deps.fs.constants.R_OK);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
reason: "INPUT_PATH_UNREADABLE",
|
|
125
|
+
message: `snapshot not readable at ${ctx.inputPath}: ${err.message}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { ok: true, mode: "snapshot-file" };
|
|
129
|
+
}
|
|
130
|
+
if (this._cookieAuth) {
|
|
131
|
+
const ok = await this._cookieAuth.validate();
|
|
132
|
+
if (!ok) return { ok: false, reason: "INVALID_COOKIE", error: "cookies missing" };
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
account: (this.account && this.account.userId) || null,
|
|
136
|
+
mode: "cookie",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
reason: "NO_INPUT",
|
|
142
|
+
message:
|
|
143
|
+
"recruit-boss.authenticate: needs opts.inputPath (snapshot mode) OR opts.account.cookies (cookie-api mode)",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async healthCheck() {
|
|
148
|
+
if (this._cookieAuth) {
|
|
149
|
+
const r = await this.authenticate();
|
|
150
|
+
return r.ok
|
|
151
|
+
? { ok: true, lastChecked: Date.now() }
|
|
152
|
+
: { ok: false, reason: r.reason, error: r.error };
|
|
153
|
+
}
|
|
154
|
+
return { ok: true, lastChecked: Date.now() };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async *sync(opts = {}) {
|
|
158
|
+
if (typeof opts.inputPath === "string" && opts.inputPath.length > 0) {
|
|
159
|
+
yield* this._syncViaSnapshot(opts);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (this._cookieAuth) {
|
|
163
|
+
yield* this._syncViaCookie(opts);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
throw new Error(
|
|
167
|
+
"recruit-boss.sync: needs opts.inputPath (snapshot mode) OR opts.account.cookies (cookie-api mode; some BOSS endpoints need __zp_stoken__ via opts.signProvider)",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async *_syncViaSnapshot(opts) {
|
|
172
|
+
const raw = this._deps.fs.readFileSync(opts.inputPath, "utf-8");
|
|
173
|
+
const snapshot = JSON.parse(raw);
|
|
174
|
+
if (
|
|
175
|
+
!snapshot ||
|
|
176
|
+
typeof snapshot !== "object" ||
|
|
177
|
+
snapshot.schemaVersion !== SNAPSHOT_SCHEMA_VERSION
|
|
178
|
+
) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`recruit-boss.sync: snapshot schemaVersion mismatch (got ${snapshot && snapshot.schemaVersion}, expected ${SNAPSHOT_SCHEMA_VERSION})`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const fallbackCapturedAt =
|
|
184
|
+
Number.isFinite(snapshot.snapshottedAt) && snapshot.snapshottedAt > 0
|
|
185
|
+
? Math.floor(snapshot.snapshottedAt)
|
|
186
|
+
: Date.now();
|
|
187
|
+
const account =
|
|
188
|
+
snapshot.account && typeof snapshot.account === "object" ? snapshot.account : null;
|
|
189
|
+
const include = opts.include || {};
|
|
190
|
+
const limit = Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : Infinity;
|
|
191
|
+
|
|
192
|
+
const events = Array.isArray(snapshot.events) ? snapshot.events : [];
|
|
193
|
+
let emitted = 0;
|
|
194
|
+
for (const ev of events) {
|
|
195
|
+
if (emitted >= limit) return;
|
|
196
|
+
if (!ev || typeof ev !== "object") continue;
|
|
197
|
+
if (!VALID_SNAPSHOT_KINDS.includes(ev.kind)) continue;
|
|
198
|
+
if (include[ev.kind] === false) continue;
|
|
199
|
+
|
|
200
|
+
const capturedAt =
|
|
201
|
+
parseTime(ev.capturedAt) ||
|
|
202
|
+
parseTime(ev.lastChatTime) ||
|
|
203
|
+
parseTime(ev.deliverTime) ||
|
|
204
|
+
fallbackCapturedAt;
|
|
205
|
+
const id =
|
|
206
|
+
(typeof ev.id === "string" && ev.id.length > 0 && ev.id) ||
|
|
207
|
+
ev.jobId ||
|
|
208
|
+
ev.hrId ||
|
|
209
|
+
null;
|
|
210
|
+
|
|
211
|
+
yield {
|
|
212
|
+
adapter: NAME,
|
|
213
|
+
kind: ev.kind,
|
|
214
|
+
originalId: stableOriginalId(ev.kind, id),
|
|
215
|
+
capturedAt,
|
|
216
|
+
payload: { ...ev, account },
|
|
217
|
+
};
|
|
218
|
+
emitted += 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async *_syncViaCookie(opts = {}) {
|
|
223
|
+
if (!(await this._cookieAuth.validate())) return;
|
|
224
|
+
const cookies = this._cookieAuth.toHeader();
|
|
225
|
+
const include = opts.include || {};
|
|
226
|
+
const limit = Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : Infinity;
|
|
227
|
+
const maxPages =
|
|
228
|
+
Number.isInteger(opts.maxPages) && opts.maxPages > 0 ? opts.maxPages : 10;
|
|
229
|
+
|
|
230
|
+
const plan = [
|
|
231
|
+
{ kind: KIND_CHAT, url: this._urls.chats, map: chatItemToRecord },
|
|
232
|
+
{ kind: KIND_APPLICATION, url: this._urls.deliveries, map: applicationItemToRecord },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
let emitted = 0;
|
|
236
|
+
for (const step of plan) {
|
|
237
|
+
if (include[step.kind] === false) continue;
|
|
238
|
+
let page = 1;
|
|
239
|
+
while (page <= maxPages) {
|
|
240
|
+
const query = { page, pageSize: PAGE_SIZE };
|
|
241
|
+
let sign = null;
|
|
242
|
+
if (this._signProvider) {
|
|
243
|
+
sign = await this._signProvider({ url: step.url, query, cookies });
|
|
244
|
+
}
|
|
245
|
+
const resp = await this._fetchFn({ url: step.url, cookies, query, sign });
|
|
246
|
+
const items = extractData(resp);
|
|
247
|
+
if (!items.length) break;
|
|
248
|
+
for (const it of items) {
|
|
249
|
+
const rec = step.map(it);
|
|
250
|
+
if (!rec) continue;
|
|
251
|
+
if (emitted >= limit) return;
|
|
252
|
+
yield {
|
|
253
|
+
adapter: NAME,
|
|
254
|
+
kind: step.kind,
|
|
255
|
+
originalId: stableOriginalId(step.kind, rec.id),
|
|
256
|
+
capturedAt: rec.occurredAt || Date.now(),
|
|
257
|
+
payload: { record: rec, kind: step.kind, cookie: true },
|
|
258
|
+
};
|
|
259
|
+
emitted += 1;
|
|
260
|
+
}
|
|
261
|
+
if (items.length < PAGE_SIZE) break;
|
|
262
|
+
page += 1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
normalize(raw) {
|
|
268
|
+
if (!raw || !raw.payload) {
|
|
269
|
+
throw new Error("BossZhipinAdapter.normalize: payload missing");
|
|
270
|
+
}
|
|
271
|
+
const ingestedAt = Date.now();
|
|
272
|
+
const kind = raw.kind || raw.payload.kind;
|
|
273
|
+
if (kind === KIND_CHAT) return normalizeChat(raw, ingestedAt);
|
|
274
|
+
if (kind === KIND_APPLICATION) return normalizeApplication(raw, ingestedAt);
|
|
275
|
+
throw new Error(`BossZhipinAdapter.normalize: unknown kind ${kind}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ─── cookie response → intermediate record ───────────────────────────────────
|
|
280
|
+
|
|
281
|
+
function extractData(resp) {
|
|
282
|
+
if (!resp || typeof resp !== "object") return [];
|
|
283
|
+
if (Array.isArray(resp.data)) return resp.data;
|
|
284
|
+
if (Array.isArray(resp.list)) return resp.list;
|
|
285
|
+
const z = resp.zpData || resp.data;
|
|
286
|
+
if (z && typeof z === "object") {
|
|
287
|
+
if (Array.isArray(z.list)) return z.list;
|
|
288
|
+
if (Array.isArray(z.result)) return z.result;
|
|
289
|
+
if (Array.isArray(z.records)) return z.records;
|
|
290
|
+
}
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function chatItemToRecord(it) {
|
|
295
|
+
if (!it || typeof it !== "object") return null;
|
|
296
|
+
const id = it.jobId || it.encryptJobId || it.bossId || it.id;
|
|
297
|
+
if (!id) return null;
|
|
298
|
+
return {
|
|
299
|
+
id: String(id),
|
|
300
|
+
jobTitle: it.jobName || it.jobTitle || it.title || "",
|
|
301
|
+
company: it.brandName || it.companyName || it.company || "",
|
|
302
|
+
hrName: it.bossName || it.hrName || it.name || null,
|
|
303
|
+
hrId: it.bossId || it.encryptBossId || it.hrId || null,
|
|
304
|
+
salary: it.salaryDesc || it.salary || null,
|
|
305
|
+
city: it.cityName || it.city || null,
|
|
306
|
+
occurredAt: parseTime(it.lastChatTime || it.updateTime || it.noneReadCountUpdateTime),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function applicationItemToRecord(it) {
|
|
311
|
+
if (!it || typeof it !== "object") return null;
|
|
312
|
+
const id = it.jobId || it.encryptJobId || it.deliverId || it.id;
|
|
313
|
+
if (!id) return null;
|
|
314
|
+
return {
|
|
315
|
+
id: String(id),
|
|
316
|
+
jobTitle: it.jobName || it.jobTitle || it.title || "",
|
|
317
|
+
company: it.brandName || it.companyName || it.company || "",
|
|
318
|
+
status: it.statusDesc || it.deliverStatus || it.status || null,
|
|
319
|
+
occurredAt: parseTime(it.deliverTime || it.addTime || it.createTime),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ─── per-kind normalizers (snapshot fields OR cookie payload.record) ──────────
|
|
324
|
+
|
|
325
|
+
function buildSource(raw, occurredAt) {
|
|
326
|
+
return {
|
|
327
|
+
adapter: NAME,
|
|
328
|
+
adapterVersion: VERSION,
|
|
329
|
+
originalId: raw.originalId,
|
|
330
|
+
capturedAt: raw.capturedAt || occurredAt,
|
|
331
|
+
capturedBy: CAPTURED_BY.API,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function normalizeChat(raw, ingestedAt) {
|
|
336
|
+
const p = raw.payload;
|
|
337
|
+
const r = p.cookie ? p.record : p;
|
|
338
|
+
const jobTitle = r.jobTitle || "";
|
|
339
|
+
const company = r.company || "";
|
|
340
|
+
const occurredAt = parseTime(r.occurredAt || r.lastChatTime || raw.capturedAt) || ingestedAt;
|
|
341
|
+
const source = buildSource(raw, occurredAt);
|
|
342
|
+
const persons = [];
|
|
343
|
+
let hrPersonId = null;
|
|
344
|
+
const hrName = r.hrName || null;
|
|
345
|
+
if (hrName) {
|
|
346
|
+
hrPersonId = `person-boss-hr-${r.hrId || hrName}`;
|
|
347
|
+
persons.push({
|
|
348
|
+
id: hrPersonId,
|
|
349
|
+
type: ENTITY_TYPES.PERSON,
|
|
350
|
+
subtype: PERSON_SUBTYPES.CONTACT,
|
|
351
|
+
names: [hrName],
|
|
352
|
+
ingestedAt,
|
|
353
|
+
source,
|
|
354
|
+
identifiers: r.hrId ? { "boss-hr-id": [String(r.hrId)] } : {},
|
|
355
|
+
extra: { platform: "boss", role: "recruiter", company: company || null },
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
events: [
|
|
360
|
+
{
|
|
361
|
+
id: newId(),
|
|
362
|
+
type: ENTITY_TYPES.EVENT,
|
|
363
|
+
subtype: EVENT_SUBTYPES.INTERACTION,
|
|
364
|
+
occurredAt,
|
|
365
|
+
actor: "person-self",
|
|
366
|
+
participants: ["person-self", ...(hrPersonId ? [hrPersonId] : [])],
|
|
367
|
+
content: {
|
|
368
|
+
title: `沟通职位: ${jobTitle}${company ? " @ " + company : ""}`,
|
|
369
|
+
text: [jobTitle, company, r.salary, r.city].filter(Boolean).join(" "),
|
|
370
|
+
},
|
|
371
|
+
ingestedAt,
|
|
372
|
+
source,
|
|
373
|
+
extra: {
|
|
374
|
+
platform: "boss",
|
|
375
|
+
jobId: r.id,
|
|
376
|
+
jobTitle: jobTitle || null,
|
|
377
|
+
company: company || null,
|
|
378
|
+
salary: r.salary || null,
|
|
379
|
+
city: r.city || null,
|
|
380
|
+
...(hrPersonId ? { hrPersonId } : {}),
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
persons,
|
|
385
|
+
places: [],
|
|
386
|
+
items: [],
|
|
387
|
+
topics: [],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function normalizeApplication(raw, ingestedAt) {
|
|
392
|
+
const p = raw.payload;
|
|
393
|
+
const r = p.cookie ? p.record : p;
|
|
394
|
+
const jobTitle = r.jobTitle || "";
|
|
395
|
+
const company = r.company || "";
|
|
396
|
+
const occurredAt = parseTime(r.occurredAt || r.deliverTime || raw.capturedAt) || ingestedAt;
|
|
397
|
+
const source = buildSource(raw, occurredAt);
|
|
398
|
+
return {
|
|
399
|
+
events: [
|
|
400
|
+
{
|
|
401
|
+
id: newId(),
|
|
402
|
+
type: ENTITY_TYPES.EVENT,
|
|
403
|
+
subtype: EVENT_SUBTYPES.INTERACTION,
|
|
404
|
+
occurredAt,
|
|
405
|
+
actor: "person-self",
|
|
406
|
+
content: {
|
|
407
|
+
title: `投递简历: ${jobTitle}${company ? " @ " + company : ""}`,
|
|
408
|
+
text: [jobTitle, company].filter(Boolean).join(" "),
|
|
409
|
+
},
|
|
410
|
+
ingestedAt,
|
|
411
|
+
source,
|
|
412
|
+
extra: {
|
|
413
|
+
platform: "boss",
|
|
414
|
+
kind: "application",
|
|
415
|
+
jobId: r.id,
|
|
416
|
+
jobTitle: jobTitle || null,
|
|
417
|
+
company: company || null,
|
|
418
|
+
status: r.status || null,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
persons: [],
|
|
423
|
+
places: [],
|
|
424
|
+
items: [],
|
|
425
|
+
topics: [],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function defaultFetch(_opts) {
|
|
430
|
+
throw new Error("recruit-boss: no fetchFn configured for cookie-api mode");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
module.exports = {
|
|
434
|
+
BossZhipinAdapter,
|
|
435
|
+
extractData,
|
|
436
|
+
chatItemToRecord,
|
|
437
|
+
applicationItemToRecord,
|
|
438
|
+
NAME,
|
|
439
|
+
VERSION,
|
|
440
|
+
SNAPSHOT_SCHEMA_VERSION,
|
|
441
|
+
VALID_SNAPSHOT_KINDS,
|
|
442
|
+
};
|