@ha7ch/job-pro 1.0.93
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/dist/adapter.js +17 -0
- package/dist/agibot.js +399 -0
- package/dist/alibaba.js +509 -0
- package/dist/antgroup.js +397 -0
- package/dist/apply.js +1373 -0
- package/dist/baichuan.js +49 -0
- package/dist/baidu.js +452 -0
- package/dist/bilibili.js +455 -0
- package/dist/byd.js +412 -0
- package/dist/bytedance.js +619 -0
- package/dist/cainiao.js +56 -0
- package/dist/cambricon.js +33 -0
- package/dist/cdp.js +237 -0
- package/dist/cicc.js +56 -0
- package/dist/coverage.js +60 -0
- package/dist/deepseek.js +25 -0
- package/dist/didi.js +381 -0
- package/dist/feishu.js +577 -0
- package/dist/galaxyuniversal.js +24 -0
- package/dist/geely.js +35 -0
- package/dist/greenhouse.js +432 -0
- package/dist/hikvision.js +58 -0
- package/dist/horizonrobotics.js +46 -0
- package/dist/hoyoverse.js +26 -0
- package/dist/huawei.js +537 -0
- package/dist/iflytek.js +380 -0
- package/dist/index.js +1828 -0
- package/dist/iqiyi.js +494 -0
- package/dist/jd.js +559 -0
- package/dist/kuaishou.js +496 -0
- package/dist/lever.js +455 -0
- package/dist/liauto.js +393 -0
- package/dist/liepin.js +357 -0
- package/dist/lilith.js +300 -0
- package/dist/megvii.js +27 -0
- package/dist/meituan.js +633 -0
- package/dist/memory.js +76 -0
- package/dist/mihoyo.js +308 -0
- package/dist/minimax.js +32 -0
- package/dist/moka.js +473 -0
- package/dist/moonshot.js +24 -0
- package/dist/netease.js +424 -0
- package/dist/nio.js +24 -0
- package/dist/oppo.js +285 -0
- package/dist/pdd.js +614 -0
- package/dist/pingan.js +493 -0
- package/dist/sensetime.js +51 -0
- package/dist/sf.js +310 -0
- package/dist/stepfun.js +24 -0
- package/dist/tencent.js +770 -0
- package/dist/trip.js +396 -0
- package/dist/unitree.js +418 -0
- package/dist/vivo.js +361 -0
- package/dist/webank.js +55 -0
- package/dist/wecruit.js +438 -0
- package/dist/weibo.js +337 -0
- package/dist/weride.js +29 -0
- package/dist/xiaohongshu.js +480 -0
- package/dist/xiaomi.js +529 -0
- package/dist/xpeng.js +34 -0
- package/dist/zerooneai.js +42 -0
- package/dist/zhipu.js +478 -0
- package/extension/README.md +79 -0
- package/extension/background.js +177 -0
- package/extension/manifest.json +55 -0
- package/extension/popup.html +37 -0
- package/extension/popup.js +54 -0
- package/package.json +61 -0
package/dist/byd.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// BYD (比亚迪) recruiting adapter — job.byd.com.
|
|
2
|
+
//
|
|
3
|
+
// ============================================================
|
|
4
|
+
// API DISCOVERY (probed 2026-05-15)
|
|
5
|
+
//
|
|
6
|
+
// The job.byd.com SPA exposes two distinct API namespaces:
|
|
7
|
+
//
|
|
8
|
+
// /portal/api/... → authenticated; every endpoint returns
|
|
9
|
+
// {"code":4001,"msg":"Token无效或已过期"}
|
|
10
|
+
// for unauthenticated requests.
|
|
11
|
+
// /portal/api/portal-api/... → ANONYMOUS public endpoints used by the SPA's
|
|
12
|
+
// home/experienced/campus landing flows. These
|
|
13
|
+
// return job listings, notices, materials, and
|
|
14
|
+
// recruit topics without any token.
|
|
15
|
+
//
|
|
16
|
+
// The working anonymous search endpoint is:
|
|
17
|
+
//
|
|
18
|
+
// POST /portal/api/portal-api/position/queryList
|
|
19
|
+
//
|
|
20
|
+
// Required headers: a normal Chrome User-Agent, Content-Type application/json,
|
|
21
|
+
// a job.byd.com Referer, and `lang: en_US` (vivo accepts both en_US and zh_CN).
|
|
22
|
+
//
|
|
23
|
+
// Body shape:
|
|
24
|
+
// {
|
|
25
|
+
// positionTypeArr: [], // 职位类型 codes
|
|
26
|
+
// positionProvinceArr: [], // 省 codes
|
|
27
|
+
// positionCityArr: [], // 市 codes
|
|
28
|
+
// positionOrgArr: [], // 事业群 codes
|
|
29
|
+
// vagueCondition: "", // free-text keyword (matches title)
|
|
30
|
+
// searchType: 1, // 1 = title search
|
|
31
|
+
// zpType: "00251", // 招聘类型 — see table below
|
|
32
|
+
// pageNum: 0, // zero-based
|
|
33
|
+
// pageSize: 20
|
|
34
|
+
// }
|
|
35
|
+
//
|
|
36
|
+
// `zpType` controls the recruit channel:
|
|
37
|
+
// "00251" 社招 (Experienced; 1647+ live postings)
|
|
38
|
+
// "00252" 技师 (Technician — empty as of probe)
|
|
39
|
+
// "00253" 操作工 (Operator / blue-collar — empty as of probe)
|
|
40
|
+
// (Campus 校招 listings live behind a separate `school/*` flow that is fully
|
|
41
|
+
// auth-gated; the public anon channel exposes social hire only.)
|
|
42
|
+
//
|
|
43
|
+
// Response envelope: {"code":0, "data":{"total":N, "data":[...]}}.
|
|
44
|
+
//
|
|
45
|
+
// Endpoint inventory (anonymous):
|
|
46
|
+
// POST /portal/api/portal-api/position/queryList → paginated jobs
|
|
47
|
+
// GET /portal/api/portal-api/material/getMaterial?ids=… → site materials
|
|
48
|
+
// POST /portal/api/portal-api/other-info/notice/query-list → notices
|
|
49
|
+
// POST /portal/api/portal-api/other-info/resource/query-list→ downloadables
|
|
50
|
+
// GET /portal/api/portal-api/common/queryCodeTree?ids=… → filter taxonomy
|
|
51
|
+
// POST /portal/api/portal-api/common/queryDeptTree → org tree
|
|
52
|
+
// POST /portal/api/portal-api/Recruitment/getMessageList → marketing msgs
|
|
53
|
+
// GET /portal/api/portal-api/resumeSend/school-topic/info?zpNature=…
|
|
54
|
+
// → campus topics
|
|
55
|
+
//
|
|
56
|
+
// ============================================================
|
|
57
|
+
import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
|
|
58
|
+
export { checkResume };
|
|
59
|
+
const SOURCE = "job.byd.com";
|
|
60
|
+
const API_ROOT = "https://job.byd.com";
|
|
61
|
+
const SITE_ROOT = "https://job.byd.com/portal/pc/";
|
|
62
|
+
const DETAIL_PAGE = (id) => `https://job.byd.com/portal/pc/#/social/detail?positionCode=${encodeURIComponent(id)}`;
|
|
63
|
+
const DEFAULT_HEADERS = {
|
|
64
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
|
65
|
+
Accept: "application/json, text/plain, */*",
|
|
66
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
67
|
+
Referer: SITE_ROOT,
|
|
68
|
+
Origin: API_ROOT,
|
|
69
|
+
lang: "zh_CN",
|
|
70
|
+
};
|
|
71
|
+
async function call(method, path, opts = {}) {
|
|
72
|
+
let url = `${API_ROOT}${path}`;
|
|
73
|
+
if (opts.query) {
|
|
74
|
+
const params = new URLSearchParams();
|
|
75
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
76
|
+
if (v !== undefined && v !== "")
|
|
77
|
+
params.set(k, String(v));
|
|
78
|
+
}
|
|
79
|
+
const qs = params.toString();
|
|
80
|
+
if (qs)
|
|
81
|
+
url += (path.includes("?") ? "&" : "?") + qs;
|
|
82
|
+
}
|
|
83
|
+
const headers = { ...DEFAULT_HEADERS };
|
|
84
|
+
let body;
|
|
85
|
+
if (opts.body !== undefined) {
|
|
86
|
+
body = JSON.stringify(opts.body);
|
|
87
|
+
headers["Content-Type"] = "application/json;charset=UTF-8";
|
|
88
|
+
}
|
|
89
|
+
let response;
|
|
90
|
+
try {
|
|
91
|
+
response = await fetch(url, { method, headers, body });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
message: `network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
|
|
101
|
+
}
|
|
102
|
+
let payload;
|
|
103
|
+
try {
|
|
104
|
+
payload = (await response.json());
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : err}` };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
ok: payload.code === 0,
|
|
111
|
+
data: payload.data,
|
|
112
|
+
message: payload.msg || payload.message || (payload.code === 0 ? "ok" : "upstream error"),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function summarize(item) {
|
|
116
|
+
const id = String(item.positionCode ?? item.id ?? "");
|
|
117
|
+
const city = [item.province, item.city].filter(Boolean).join("·");
|
|
118
|
+
return {
|
|
119
|
+
post_id: id,
|
|
120
|
+
title: (item.positionName ?? "").trim(),
|
|
121
|
+
project: (item.fatherOrgAliasName ?? item.fatherOrgName ?? "").trim(),
|
|
122
|
+
recruit_label: "社招",
|
|
123
|
+
bgs: (item.orgAliasName ?? item.orgName ?? "").trim(),
|
|
124
|
+
work_cities: city,
|
|
125
|
+
apply_url: id ? DETAIL_PAGE(id) : SITE_ROOT,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ---------- searchPositions ----------
|
|
129
|
+
export async function searchPositions(opts = {}) {
|
|
130
|
+
const pageSize = Math.max(1, Math.min(50, opts.pageSize ?? 20));
|
|
131
|
+
const page = Math.max(1, opts.page ?? 1);
|
|
132
|
+
const body = {
|
|
133
|
+
positionTypeArr: opts.positionTypeIds ?? [],
|
|
134
|
+
positionProvinceArr: opts.provinceCodes ?? [],
|
|
135
|
+
positionCityArr: opts.cityCodes ?? [],
|
|
136
|
+
positionOrgArr: opts.orgCodes ?? [],
|
|
137
|
+
vagueCondition: (opts.keyword ?? "").trim().slice(0, 60),
|
|
138
|
+
searchType: 1,
|
|
139
|
+
zpType: opts.zpType ?? "00251",
|
|
140
|
+
pageNum: page - 1, // BYD uses 0-based
|
|
141
|
+
pageSize,
|
|
142
|
+
};
|
|
143
|
+
const r = await call("POST", "/portal/api/portal-api/position/queryList", { body });
|
|
144
|
+
if (!r.ok || !r.data) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
source: SOURCE,
|
|
148
|
+
message: r.message,
|
|
149
|
+
query: body,
|
|
150
|
+
positions: [],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const rows = r.data.data ?? [];
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
source: SOURCE,
|
|
157
|
+
query: body,
|
|
158
|
+
page,
|
|
159
|
+
page_size: pageSize,
|
|
160
|
+
total: r.data.total ?? rows.length,
|
|
161
|
+
positions: rows.map(summarize),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// ---------- fetchAllPositions ----------
|
|
165
|
+
export async function fetchAllPositions(opts = {}) {
|
|
166
|
+
const pageSize = Math.max(1, Math.min(50, opts.pageSize ?? 50));
|
|
167
|
+
const maxPages = Math.max(1, opts.maxPages ?? 40);
|
|
168
|
+
const bucket = [];
|
|
169
|
+
let total;
|
|
170
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
171
|
+
const r = await searchPositions({
|
|
172
|
+
keyword: opts.keyword,
|
|
173
|
+
page,
|
|
174
|
+
pageSize,
|
|
175
|
+
zpType: opts.zpType,
|
|
176
|
+
});
|
|
177
|
+
if (!r.ok) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
source: SOURCE,
|
|
181
|
+
message: r.message,
|
|
182
|
+
total: 0,
|
|
183
|
+
fetched: bucket.length,
|
|
184
|
+
positions: bucket,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (total === undefined)
|
|
188
|
+
total = r.total;
|
|
189
|
+
if (!r.positions.length)
|
|
190
|
+
break;
|
|
191
|
+
bucket.push(...r.positions);
|
|
192
|
+
if (total !== undefined && bucket.length >= total)
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
source: SOURCE,
|
|
198
|
+
total: total ?? bucket.length,
|
|
199
|
+
fetched: bucket.length,
|
|
200
|
+
positions: bucket,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// ---------- fetchPositionDetail ----------
|
|
204
|
+
//
|
|
205
|
+
// The detail endpoint /portal/api/position/queryDetail requires auth, but the
|
|
206
|
+
// public list endpoint returns enough info per row that we surface a "row+link"
|
|
207
|
+
// detail instead of a fully gated 4001 stub.
|
|
208
|
+
export async function fetchPositionDetail(postId) {
|
|
209
|
+
const id = (postId ?? "").trim();
|
|
210
|
+
if (!id)
|
|
211
|
+
return { ok: false, source: SOURCE, message: "post_id is required", post_id: id };
|
|
212
|
+
// Page through the social-hire list looking for the row. This is the best we
|
|
213
|
+
// can do without a logged-in JWT; in practice the row is usually within the
|
|
214
|
+
// first few hundred records and matchResume already pages through the full
|
|
215
|
+
// catalogue.
|
|
216
|
+
const r = await searchPositions({ keyword: id, pageSize: 5 });
|
|
217
|
+
const hit = r.ok ? r.positions.find((p) => p.post_id === id) : undefined;
|
|
218
|
+
if (!hit) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
source: SOURCE,
|
|
222
|
+
message: "Position detail endpoint (POST /portal/api/position/queryDetail) requires a logged-in JWT. " +
|
|
223
|
+
"Public anon API can list positions but not return per-position bodies.",
|
|
224
|
+
post_id: id,
|
|
225
|
+
apply_url: DETAIL_PAGE(id),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
source: SOURCE,
|
|
231
|
+
post_id: hit.post_id,
|
|
232
|
+
title: hit.title,
|
|
233
|
+
project: hit.project,
|
|
234
|
+
bgs: hit.bgs,
|
|
235
|
+
recruit_label: hit.recruit_label,
|
|
236
|
+
work_cities: hit.work_cities,
|
|
237
|
+
description: "",
|
|
238
|
+
requirements: "",
|
|
239
|
+
apply_url: hit.apply_url,
|
|
240
|
+
note: "Description and requirements are not available without authentication; " +
|
|
241
|
+
"visit apply_url for the full posting after login.",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// ---------- fetchDictionaries ----------
|
|
245
|
+
export async function fetchDictionaries() {
|
|
246
|
+
const [codeTree, deptTree] = await Promise.all([
|
|
247
|
+
call("GET", "/portal/api/portal-api/common/queryCodeTree", {
|
|
248
|
+
query: { ids: "0009,0030" },
|
|
249
|
+
}),
|
|
250
|
+
call("POST", "/portal/api/portal-api/common/queryDeptTree", { body: {} }),
|
|
251
|
+
]);
|
|
252
|
+
return {
|
|
253
|
+
ok: codeTree.ok || deptTree.ok,
|
|
254
|
+
source: SOURCE,
|
|
255
|
+
api_host: API_ROOT,
|
|
256
|
+
verified_at: new Date().toISOString(),
|
|
257
|
+
code_tree: codeTree.data ?? null,
|
|
258
|
+
dept_tree: deptTree.data ?? null,
|
|
259
|
+
zp_types: {
|
|
260
|
+
"00251": "社招 (Experienced)",
|
|
261
|
+
"00252": "技师 (Technician)",
|
|
262
|
+
"00253": "操作工 (Operator)",
|
|
263
|
+
},
|
|
264
|
+
note: "Campus (校招) jobs are not exposed by the anon public API — the school/* " +
|
|
265
|
+
"endpoints all require a JWT bearer token.",
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export async function listNotices() {
|
|
269
|
+
const r = await call("POST", "/portal/api/portal-api/other-info/notice/query-list", { body: { pageNum: 0, pageSize: 30 } });
|
|
270
|
+
if (!r.ok)
|
|
271
|
+
return { ok: false, source: SOURCE, message: r.message, notices: [] };
|
|
272
|
+
const items = r.data?.data ?? r.data?.list ?? [];
|
|
273
|
+
return {
|
|
274
|
+
ok: true,
|
|
275
|
+
source: SOURCE,
|
|
276
|
+
count: items.length,
|
|
277
|
+
notices: items.map((n) => ({
|
|
278
|
+
id: String(n.id ?? ""),
|
|
279
|
+
title: n.title ?? n.noticeTitle ?? "",
|
|
280
|
+
publish_time: n.publishTime ?? n.createTime ?? "",
|
|
281
|
+
tag: n.noticeType ?? "",
|
|
282
|
+
detail_url: SITE_ROOT,
|
|
283
|
+
})),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
export async function getNotice(noticeId) {
|
|
287
|
+
const id = (noticeId ?? "").trim();
|
|
288
|
+
if (!id)
|
|
289
|
+
return { ok: false, source: SOURCE, message: "notice_id is required" };
|
|
290
|
+
const all = await listNotices();
|
|
291
|
+
if (!all.ok)
|
|
292
|
+
return { ok: false, source: SOURCE, message: all.message };
|
|
293
|
+
const hit = all.notices.find((n) => n.id === id);
|
|
294
|
+
if (!hit)
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
source: SOURCE,
|
|
298
|
+
message: `notice ${id} not in the latest /notice/query-list page`,
|
|
299
|
+
};
|
|
300
|
+
return { ok: true, source: SOURCE, ...hit, content_html: "" };
|
|
301
|
+
}
|
|
302
|
+
export async function findNoticesByQuestion(question, opts = {}) {
|
|
303
|
+
const listing = await listNotices();
|
|
304
|
+
if (!listing.ok)
|
|
305
|
+
return { ok: false, source: SOURCE, message: listing.message, matches: [] };
|
|
306
|
+
const tokens = [];
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
const text = (question ?? "").trim();
|
|
309
|
+
for (const m of text.match(/[A-Za-z0-9]{2,}/g) ?? []) {
|
|
310
|
+
const k = m.toLowerCase();
|
|
311
|
+
if (!seen.has(k)) {
|
|
312
|
+
seen.add(k);
|
|
313
|
+
tokens.push(k);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const run of text.match(/[一-鿿]+/g) ?? []) {
|
|
317
|
+
for (let i = 0; i < run.length - 1; i++) {
|
|
318
|
+
const bigram = run.slice(i, i + 2);
|
|
319
|
+
if (!seen.has(bigram)) {
|
|
320
|
+
seen.add(bigram);
|
|
321
|
+
tokens.push(bigram);
|
|
322
|
+
}
|
|
323
|
+
if (tokens.length >= 40)
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const topK = Math.max(1, opts.topK ?? 3);
|
|
328
|
+
const scored = listing.notices
|
|
329
|
+
.map((n) => {
|
|
330
|
+
const hay = `${n.title} ${n.tag}`.toLowerCase();
|
|
331
|
+
const score = tokens.filter((t) => hay.includes(t)).length;
|
|
332
|
+
return { score, notice: n };
|
|
333
|
+
})
|
|
334
|
+
.filter((s) => s.score > 0)
|
|
335
|
+
.sort((a, b) => b.score - a.score);
|
|
336
|
+
return {
|
|
337
|
+
ok: true,
|
|
338
|
+
source: SOURCE,
|
|
339
|
+
question,
|
|
340
|
+
question_time: opts.questionTime,
|
|
341
|
+
matched_tokens: tokens,
|
|
342
|
+
matches: scored.slice(0, topK).map((s) => ({ ...s.notice, excerpt: "" })),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
// ---------- matchResume ----------
|
|
346
|
+
export async function matchResume(text, opts = {}) {
|
|
347
|
+
const { terms, cities } = extractResumeSignals(text ?? "");
|
|
348
|
+
const topN = Math.max(1, opts.topN ?? 5);
|
|
349
|
+
const candidates = Math.max(topN, opts.candidates ?? 200);
|
|
350
|
+
const all = await fetchAllPositions({
|
|
351
|
+
pageSize: 50,
|
|
352
|
+
maxPages: Math.ceil(candidates / 50),
|
|
353
|
+
});
|
|
354
|
+
if (!all.ok) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
source: SOURCE,
|
|
358
|
+
message: all.message,
|
|
359
|
+
extracted_terms: terms,
|
|
360
|
+
city_preferences: cities,
|
|
361
|
+
matches: [],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const scored = [];
|
|
365
|
+
for (const p of all.positions) {
|
|
366
|
+
const haystack = `${p.title} ${p.project} ${p.bgs} ${p.work_cities}`;
|
|
367
|
+
const score = scoreOverlap(haystack, terms, cities).score;
|
|
368
|
+
if (score > 0)
|
|
369
|
+
scored.push({ score, position: p });
|
|
370
|
+
}
|
|
371
|
+
scored.sort((a, b) => b.score - a.score);
|
|
372
|
+
return {
|
|
373
|
+
ok: true,
|
|
374
|
+
source: SOURCE,
|
|
375
|
+
extracted_terms: terms,
|
|
376
|
+
city_preferences: cities,
|
|
377
|
+
candidate_pool: all.positions.length,
|
|
378
|
+
matches: scored.slice(0, topN).map((s) => s.position),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
export { extractResumeSignals, scoreOverlap };
|
|
382
|
+
import { buildBespokeApplySchema as _buildBespokeApplySchema_byd } from "./apply.js";
|
|
383
|
+
export async function fetchApplicationSchema(postId) {
|
|
384
|
+
const id = (postId ?? "").trim();
|
|
385
|
+
if (!id)
|
|
386
|
+
return { ok: false, source: "job.byd.com", message: "post_id is required" };
|
|
387
|
+
let title = "";
|
|
388
|
+
let applyUrl = "https://job.byd.com";
|
|
389
|
+
try {
|
|
390
|
+
const detail = (await fetchPositionDetail(id));
|
|
391
|
+
if (detail?.ok === false) {
|
|
392
|
+
return { ok: false, source: "job.byd.com", message: detail.message ?? "post not found" };
|
|
393
|
+
}
|
|
394
|
+
title = detail?.title ?? "";
|
|
395
|
+
if (detail?.apply_url)
|
|
396
|
+
applyUrl = detail.apply_url;
|
|
397
|
+
}
|
|
398
|
+
catch { }
|
|
399
|
+
return {
|
|
400
|
+
ok: true,
|
|
401
|
+
schema: _buildBespokeApplySchema_byd({
|
|
402
|
+
source: "job.byd.com",
|
|
403
|
+
postId: id,
|
|
404
|
+
jobTitle: title,
|
|
405
|
+
applyUrl,
|
|
406
|
+
submitEndpoint: "https://job.byd.com/portal/api/portal-api/resume/apply",
|
|
407
|
+
submitKind: "multipart-session",
|
|
408
|
+
endpointVerified: true,
|
|
409
|
+
submitNotes: "BYD — POST /portal/api/portal-api/resume/apply with JWT bearer (Token). Endpoint anon-probed → HTTP 200 + {code:4001, msg:\"Token无效或已过期: Not Authenticated\"} (unified gateway token middleware; the originally-inferred /position/apply returns structured 404 from the Spring position service, but /resume/apply, /job/apply, /applicant/apply, /resume/submit, /career/apply all hit the auth gateway). Body shape still needs validation against a real candidate session.",
|
|
410
|
+
}),
|
|
411
|
+
};
|
|
412
|
+
}
|