@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/kuaishou.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
// Thin client for Kuaishou's public campus-recruiting API at campus.kuaishou.cn.
|
|
2
|
+
//
|
|
3
|
+
// All endpoints are unauthenticated; the server enforces Referer to discourage
|
|
4
|
+
// cross-site embedding. The campus portal (formerly zhaopin.kuaishou.com, now
|
|
5
|
+
// redirected to zhaopin.kuaishou.cn, with the actual API on campus.kuaishou.cn)
|
|
6
|
+
// is a React SPA backed by a Spring-Boot JSON API.
|
|
7
|
+
//
|
|
8
|
+
// ============================================================
|
|
9
|
+
// API discovery (probed 2026-05, campus JS bundle main.e3c87842.js):
|
|
10
|
+
//
|
|
11
|
+
// Base: https://campus.kuaishou.cn/recruit/campus/e
|
|
12
|
+
//
|
|
13
|
+
// POST /api/v1/open/positions/simple
|
|
14
|
+
// Payload: { pageNum, pageSize, positionCategoryCodes?, workLocationCodes?,
|
|
15
|
+
// positionNatureCode?, positionLabel?, recruitSubProjectCode? }
|
|
16
|
+
// Response: { code:0, message:"OK", result:{ total, list:[...], pages, ... } }
|
|
17
|
+
// Note: the `keyword` field is accepted but silently ignored — the server
|
|
18
|
+
// returns the full unfiltered list regardless of keyword value. There is
|
|
19
|
+
// no functional server-side text search on this endpoint.
|
|
20
|
+
// Default (no filter): 441 positions total (校招 + 实习 combined).
|
|
21
|
+
//
|
|
22
|
+
// GET /api/v1/dictionary/{type} (type is a literal path segment)
|
|
23
|
+
// GET /api/v1/dictionary/positionCategory → full 2-level category tree.
|
|
24
|
+
// GET /api/v1/dictionary/workLocation → all city codes + names.
|
|
25
|
+
//
|
|
26
|
+
// GET /api/v1/open/sub-project/list
|
|
27
|
+
// Returns the full list of recruit sub-projects (年度招聘批次) including:
|
|
28
|
+
// "20261749721165" = 2026应届生 (fulltime, active)
|
|
29
|
+
// "20261707035672" = 2026实习生 (intern, active)
|
|
30
|
+
// "20251718874803" = 2025应届生 (fulltime, active)
|
|
31
|
+
// "20251707035672" = 2025实习生 (intern, active)
|
|
32
|
+
// ... and older cohorts
|
|
33
|
+
//
|
|
34
|
+
// ============================================================
|
|
35
|
+
// Filter semantics (probed 2026-05):
|
|
36
|
+
// positionNatureCode="fulltime" → 校招/正式 (~207 posts — matches 校园招聘 tab)
|
|
37
|
+
// positionNatureCode="intern" → 实习 (~234 posts)
|
|
38
|
+
// No positionNatureCode → all (~441 posts)
|
|
39
|
+
// recruitSubProjectCode=code → specific cohort (e.g. 2026届正式 = 205 posts)
|
|
40
|
+
// positionLabel="kstar" → 快Star-X elite track (~77 posts)
|
|
41
|
+
// workLocationCodes=["beijing"] → Beijing only (~419 posts across all types)
|
|
42
|
+
// positionCategoryCodes=["algorithm"] → algorithm category (~163 posts)
|
|
43
|
+
//
|
|
44
|
+
// ============================================================
|
|
45
|
+
// Position category taxonomy (GET /api/v1/dictionary/positionCategory, 2026-05):
|
|
46
|
+
//
|
|
47
|
+
// Parent "algorithm" 算法类
|
|
48
|
+
// J1001 机器学习 J1002 数据科学 J1003 自然语言处理
|
|
49
|
+
// J1004 搜索 J1005 推荐 J1006 广告
|
|
50
|
+
// J1007 计算机视觉 J1008 计算机图形学 J1009 视频增强和处理
|
|
51
|
+
// J1010 音频处理 J1011 视频编解码 J1012 网络传输
|
|
52
|
+
// J1013 系统架构
|
|
53
|
+
// Parent "engeering" 工程类 (note: upstream typo)
|
|
54
|
+
// J1014 服务端 J1015 前端 J1016 客户端
|
|
55
|
+
// J1017 测试测开 J1018 数据研发 J1019 安全
|
|
56
|
+
// J1020 系统架构
|
|
57
|
+
// Parent "production" 产品类
|
|
58
|
+
// J1021 策略产品 J1022 用户产品C端 J1023 海外产品
|
|
59
|
+
// J1024 平台产品B端 J1025 数据产品 J1026 产品运营
|
|
60
|
+
// Parent "operation" 运营类
|
|
61
|
+
// J1027 客户运营 J1028 用户运营 J1029 内容运营
|
|
62
|
+
// J1030 策略运营 J1031 渠道运营 J1032 行业运营
|
|
63
|
+
// J1033 社区安全运营 J1034 内容质量运营 J1035 海外运营 J1036 业务运营
|
|
64
|
+
// Parent "marketing" 市场类 (no children in active list)
|
|
65
|
+
// Parent "design" 设计类 (no children in active list)
|
|
66
|
+
// Parent "function" 职能类 (no children in active list)
|
|
67
|
+
// Parent "analysis" 战略分析类 (no children in active list)
|
|
68
|
+
// Parent "gamePlanning" 游戏类 (no children in active list)
|
|
69
|
+
// Parent "PM" 项目管理类 (no children in active list)
|
|
70
|
+
// Parent "sales" 销售类 (no children in active list)
|
|
71
|
+
//
|
|
72
|
+
// ============================================================
|
|
73
|
+
// City codes (GET /api/v1/dictionary/workLocation, 2026-05, 38 total):
|
|
74
|
+
// beijing=北京 shanghai=上海 Guangzhou=广州 Shenzhen=深圳 Hangzhou=杭州
|
|
75
|
+
// suzhou=苏州 Wuhan=武汉 Chengdu=成都 Tianjin=天津 Jinan=济南
|
|
76
|
+
// qingdao=青岛 zhengzhou=郑州 chongqing=重庆 changsha=长沙 dalian=大连
|
|
77
|
+
// Haerbin=哈尔滨 Shenyang=沈阳 Singapore=新加坡 and more.
|
|
78
|
+
//
|
|
79
|
+
// ============================================================
|
|
80
|
+
// NOTE on keyword search: The positions/simple endpoint does NOT support
|
|
81
|
+
// server-side text search. Client-side filtering is applied in matchResume()
|
|
82
|
+
// by scoring position name + description + demand against resume signals.
|
|
83
|
+
//
|
|
84
|
+
// ============================================================
|
|
85
|
+
// ---- PositionSummary field mapping (Kuaishou → canonical) ----
|
|
86
|
+
// post_id ← item.code (UUID string, stable, used in detail URL)
|
|
87
|
+
// title ← item.name
|
|
88
|
+
// project ← item.positionCategoryCode (e.g. "J1014" = 服务端)
|
|
89
|
+
// recruit_label ← item.positionNatureCode ("fulltime" or "intern")
|
|
90
|
+
// bgs ← "" (Kuaishou does not expose BG in public API)
|
|
91
|
+
// work_cities ← item.workLocationDicts[*].name joined with " / "
|
|
92
|
+
// apply_url ← https://campus.kuaishou.cn/recruit/campus/e/#/campus/job-info/?code={code}
|
|
93
|
+
import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
|
|
94
|
+
export { checkResume };
|
|
95
|
+
const API_BASE = "https://campus.kuaishou.cn/recruit/campus/e";
|
|
96
|
+
const CAMPUS_PAGE = `${API_BASE}/#/campus/index/`;
|
|
97
|
+
const DETAIL_URL = (code) => `${API_BASE}/#/campus/job-info/?code=${encodeURIComponent(code)}`;
|
|
98
|
+
const DEFAULT_HEADERS = {
|
|
99
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
100
|
+
Accept: "application/json, text/plain, */*",
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
Referer: CAMPUS_PAGE,
|
|
103
|
+
Origin: "https://campus.kuaishou.cn",
|
|
104
|
+
};
|
|
105
|
+
async function post(path, body) {
|
|
106
|
+
const url = `${API_BASE}${path}`;
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await fetch(url, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: DEFAULT_HEADERS,
|
|
112
|
+
body: JSON.stringify(body),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
message: `network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
|
|
123
|
+
}
|
|
124
|
+
let payload;
|
|
125
|
+
try {
|
|
126
|
+
payload = (await response.json());
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
ok: payload.code === 0,
|
|
133
|
+
data: payload.result,
|
|
134
|
+
message: payload.message ?? (payload.code === 0 ? "ok" : "upstream error"),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function get(path) {
|
|
138
|
+
const url = `${API_BASE}${path}`;
|
|
139
|
+
let response;
|
|
140
|
+
try {
|
|
141
|
+
response = await fetch(url, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
headers: DEFAULT_HEADERS,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
message: `network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
|
|
154
|
+
}
|
|
155
|
+
let payload;
|
|
156
|
+
try {
|
|
157
|
+
payload = (await response.json());
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
ok: payload.code === 0,
|
|
164
|
+
data: payload.result,
|
|
165
|
+
message: payload.message ?? (payload.code === 0 ? "ok" : "upstream error"),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function summarizePosition(item) {
|
|
169
|
+
const code = item.code ?? String(item.id ?? "");
|
|
170
|
+
const cities = (item.workLocationDicts ?? [])
|
|
171
|
+
.map((c) => c.name ?? "")
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
.join(" / ");
|
|
174
|
+
return {
|
|
175
|
+
post_id: code,
|
|
176
|
+
title: item.name ?? "",
|
|
177
|
+
project: item.positionCategoryCode ?? "",
|
|
178
|
+
recruit_label: item.positionNatureCode ?? "",
|
|
179
|
+
bgs: "",
|
|
180
|
+
work_cities: cities,
|
|
181
|
+
apply_url: code ? DETAIL_URL(code) : CAMPUS_PAGE,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function buildPayload(opts, pageNum, pageSize) {
|
|
185
|
+
const payload = { pageNum, pageSize };
|
|
186
|
+
if (opts.positionNatureCode)
|
|
187
|
+
payload.positionNatureCode = opts.positionNatureCode;
|
|
188
|
+
if (opts.positionCategoryCodes?.length)
|
|
189
|
+
payload.positionCategoryCodes = opts.positionCategoryCodes;
|
|
190
|
+
if (opts.workLocationCodes?.length)
|
|
191
|
+
payload.workLocationCodes = opts.workLocationCodes;
|
|
192
|
+
if (opts.positionLabel)
|
|
193
|
+
payload.positionLabel = opts.positionLabel;
|
|
194
|
+
if (opts.recruitSubProjectCode)
|
|
195
|
+
payload.recruitSubProjectCode = opts.recruitSubProjectCode;
|
|
196
|
+
return payload;
|
|
197
|
+
}
|
|
198
|
+
// ---------- searchPositions ----------
|
|
199
|
+
export async function searchPositions(opts = {}) {
|
|
200
|
+
const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 20));
|
|
201
|
+
const page = Math.max(1, opts.page ?? 1);
|
|
202
|
+
const payload = buildPayload(opts, page, pageSize);
|
|
203
|
+
const response = await post("/api/v1/open/positions/simple", payload);
|
|
204
|
+
if (!response.ok || !response.data) {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
message: response.message,
|
|
208
|
+
source: "campus.kuaishou.cn",
|
|
209
|
+
query: payload,
|
|
210
|
+
positions: [],
|
|
211
|
+
total: 0,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const rows = response.data.list ?? [];
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
source: "campus.kuaishou.cn",
|
|
218
|
+
query: payload,
|
|
219
|
+
page,
|
|
220
|
+
page_size: pageSize,
|
|
221
|
+
total: response.data.total ?? rows.length,
|
|
222
|
+
positions: rows.map(summarizePosition),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// ---------- fetchAllPositions ----------
|
|
226
|
+
export async function fetchAllPositions(opts = {}) {
|
|
227
|
+
const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 100));
|
|
228
|
+
const maxPages = Math.max(1, opts.maxPages ?? 5);
|
|
229
|
+
const bucket = [];
|
|
230
|
+
let total;
|
|
231
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
232
|
+
const result = await searchPositions({ ...opts, page, pageSize });
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
message: result.message,
|
|
237
|
+
source: "campus.kuaishou.cn",
|
|
238
|
+
fetched: bucket.length,
|
|
239
|
+
positions: bucket,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (total === undefined)
|
|
243
|
+
total = result.total;
|
|
244
|
+
if (!result.positions.length)
|
|
245
|
+
break;
|
|
246
|
+
bucket.push(...result.positions);
|
|
247
|
+
if (total !== undefined && bucket.length >= total)
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
source: "campus.kuaishou.cn",
|
|
253
|
+
total: total ?? bucket.length,
|
|
254
|
+
fetched: bucket.length,
|
|
255
|
+
positions: bucket,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// ---------- fetchPositionDetail ----------
|
|
259
|
+
// Kuaishou's public /api/v1/open/position?code= endpoint returns code:1 for
|
|
260
|
+
// external requests — the detail HTML is rendered client-side from the same
|
|
261
|
+
// data already returned in the list. We approximate detail by scanning up to
|
|
262
|
+
// 5 pages of 100 for the matching post_id (which is item.code).
|
|
263
|
+
export async function fetchPositionDetail(postId) {
|
|
264
|
+
const id = (postId ?? "").trim();
|
|
265
|
+
if (!id)
|
|
266
|
+
return { ok: false, source: "campus.kuaishou.cn", message: "post_id is required" };
|
|
267
|
+
const pageSize = 100;
|
|
268
|
+
const maxPages = 5;
|
|
269
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
270
|
+
const payload = buildPayload({}, page, pageSize);
|
|
271
|
+
const response = await post("/api/v1/open/positions/simple", payload);
|
|
272
|
+
if (!response.ok || !response.data)
|
|
273
|
+
break;
|
|
274
|
+
const posts = response.data.list ?? [];
|
|
275
|
+
const found = posts.find((p) => (p.code ?? String(p.id ?? "")) === id);
|
|
276
|
+
if (found) {
|
|
277
|
+
const summary = summarizePosition(found);
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
source: "campus.kuaishou.cn",
|
|
281
|
+
post_id: id,
|
|
282
|
+
title: found.name ?? "",
|
|
283
|
+
direction: found.positionCategoryCode ?? "",
|
|
284
|
+
description: found.description ?? "",
|
|
285
|
+
requirements: found.positionDemand ?? "",
|
|
286
|
+
work_cities: found.workLocationDicts ?? [],
|
|
287
|
+
recruit_label: found.positionNatureCode ?? "",
|
|
288
|
+
release_time: found.releaseTime ?? "",
|
|
289
|
+
apply_url: summary.apply_url,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (posts.length < pageSize)
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
source: "campus.kuaishou.cn",
|
|
298
|
+
post_id: id,
|
|
299
|
+
message: `post ${id} not found in public search results (searched up to ${maxPages * pageSize} posts)`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
let _dictCache = null;
|
|
303
|
+
export async function fetchDictionaries() {
|
|
304
|
+
if (_dictCache !== null)
|
|
305
|
+
return _dictCache;
|
|
306
|
+
const [catRes, cityRes, subProjRes] = await Promise.all([
|
|
307
|
+
get("/api/v1/dictionary/positionCategory"),
|
|
308
|
+
get("/api/v1/dictionary/workLocation"),
|
|
309
|
+
get("/api/v1/open/sub-project/list"),
|
|
310
|
+
]);
|
|
311
|
+
const anyFailed = !catRes.ok || !cityRes.ok || !subProjRes.ok;
|
|
312
|
+
if (anyFailed && !catRes.ok) {
|
|
313
|
+
const r = { ok: false, source: "campus.kuaishou.cn", message: catRes.message };
|
|
314
|
+
_dictCache = r;
|
|
315
|
+
return r;
|
|
316
|
+
}
|
|
317
|
+
const positionCategories = (catRes.data ?? []).map((cat) => ({
|
|
318
|
+
code: cat.code ?? "",
|
|
319
|
+
name: cat.name ?? "",
|
|
320
|
+
parentCode: cat.parentCode ?? null,
|
|
321
|
+
children: (cat.children ?? []).map((c) => ({
|
|
322
|
+
code: c.code ?? "",
|
|
323
|
+
name: c.name ?? "",
|
|
324
|
+
})),
|
|
325
|
+
}));
|
|
326
|
+
const cities = (cityRes.data ?? []).map((c) => ({
|
|
327
|
+
code: c.code ?? "",
|
|
328
|
+
name: c.name ?? "",
|
|
329
|
+
}));
|
|
330
|
+
const subProjects = (subProjRes.data?.list ?? []).map((p) => ({
|
|
331
|
+
code: p.code ?? "",
|
|
332
|
+
name: p.name ?? "",
|
|
333
|
+
projectType: p.projectType ?? "",
|
|
334
|
+
year: p.year ?? "",
|
|
335
|
+
active: Boolean(p.active),
|
|
336
|
+
startTime: p.startTime ?? "",
|
|
337
|
+
}));
|
|
338
|
+
const positionNatureCodes = [
|
|
339
|
+
{ code: "fulltime", note: "校招/正式 (~207 active posts)" },
|
|
340
|
+
{ code: "intern", note: "实习 (~234 active posts)" },
|
|
341
|
+
];
|
|
342
|
+
const result = {
|
|
343
|
+
ok: true,
|
|
344
|
+
source: "campus.kuaishou.cn",
|
|
345
|
+
positionCategories,
|
|
346
|
+
cities,
|
|
347
|
+
subProjects,
|
|
348
|
+
positionNatureCodes,
|
|
349
|
+
};
|
|
350
|
+
_dictCache = result;
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
// ---------- stub notices ----------
|
|
354
|
+
// campus.kuaishou.cn has no public notices/announcements API.
|
|
355
|
+
const STUB_SRC = "campus.kuaishou.cn";
|
|
356
|
+
const STUB_MSG = "Kuaishou: no public notices endpoint";
|
|
357
|
+
export async function listNotices() {
|
|
358
|
+
return { ok: false, source: STUB_SRC, message: STUB_MSG };
|
|
359
|
+
}
|
|
360
|
+
export async function getNotice(_id) {
|
|
361
|
+
return { ok: false, source: STUB_SRC, message: STUB_MSG };
|
|
362
|
+
}
|
|
363
|
+
export async function findNoticesByQuestion(_question, _opts = {}) {
|
|
364
|
+
return { ok: false, source: STUB_SRC, message: STUB_MSG };
|
|
365
|
+
}
|
|
366
|
+
// ---------- matchResume ----------
|
|
367
|
+
// 1. Extract resume signals (tech terms + city preferences) via shared helpers.
|
|
368
|
+
// 2. Fetch all positions (up to 5 pages × 100) — no server-side keyword filter.
|
|
369
|
+
// 3. Score each position against title + category + description + demand blob.
|
|
370
|
+
// 4. Return top N matches with reasons.
|
|
371
|
+
export async function matchResume(text, opts = {}) {
|
|
372
|
+
const topN = Math.max(1, opts.topN ?? 5);
|
|
373
|
+
const candidates = Math.max(topN, opts.candidates ?? 20);
|
|
374
|
+
const { terms, cities } = extractResumeSignals(text ?? "");
|
|
375
|
+
if (!terms.length) {
|
|
376
|
+
return {
|
|
377
|
+
ok: false,
|
|
378
|
+
source: "campus.kuaishou.cn",
|
|
379
|
+
message: "could not extract any technical signals from the text",
|
|
380
|
+
preview: (text ?? "").slice(0, 120),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
// Fetch a broad pool of posts (defaults to fulltime, up to 500 posts).
|
|
384
|
+
const pool = await fetchAllPositions({ pageSize: 100, maxPages: 5 });
|
|
385
|
+
if (!pool.ok) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
source: "campus.kuaishou.cn",
|
|
389
|
+
message: pool.message,
|
|
390
|
+
positions: [],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// We already have description + positionDemand in the list response, so no
|
|
394
|
+
// second fetch is needed. We need raw items for those fields though — re-fetch
|
|
395
|
+
// 1 page to get raw data. Actually the full data is in PositionSummary's
|
|
396
|
+
// associated raw items held in pool; since we only have summaries at this point,
|
|
397
|
+
// re-fetch page 1 raw to build a lookup.
|
|
398
|
+
const rawLookup = new Map();
|
|
399
|
+
for (let pg = 1; pg <= 5; pg++) {
|
|
400
|
+
const payload = buildPayload({}, pg, 100);
|
|
401
|
+
const r = await post("/api/v1/open/positions/simple", payload);
|
|
402
|
+
if (!r.ok || !r.data)
|
|
403
|
+
break;
|
|
404
|
+
for (const item of r.data.list ?? []) {
|
|
405
|
+
const code = item.code ?? String(item.id ?? "");
|
|
406
|
+
if (code)
|
|
407
|
+
rawLookup.set(code, item);
|
|
408
|
+
}
|
|
409
|
+
if ((r.data.list?.length ?? 0) < 100)
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
const scored = [];
|
|
413
|
+
for (const p of pool.positions) {
|
|
414
|
+
const raw = rawLookup.get(p.post_id);
|
|
415
|
+
const blob = [
|
|
416
|
+
p.title,
|
|
417
|
+
p.project,
|
|
418
|
+
p.recruit_label,
|
|
419
|
+
p.work_cities,
|
|
420
|
+
raw?.description ?? "",
|
|
421
|
+
raw?.positionDemand ?? "",
|
|
422
|
+
].join(" ");
|
|
423
|
+
const { score, reasons } = scoreOverlap(blob, terms, cities);
|
|
424
|
+
if (score > 0) {
|
|
425
|
+
scored.push({
|
|
426
|
+
score,
|
|
427
|
+
position: p,
|
|
428
|
+
reasons,
|
|
429
|
+
description: raw?.description,
|
|
430
|
+
requirements: raw?.positionDemand,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
scored.sort((a, b) => b.score - a.score);
|
|
435
|
+
let shortlist = scored.slice(0, Math.max(topN, candidates));
|
|
436
|
+
if (!shortlist.length) {
|
|
437
|
+
shortlist = pool.positions.slice(0, candidates).map((position) => ({
|
|
438
|
+
score: 0,
|
|
439
|
+
position,
|
|
440
|
+
reasons: [],
|
|
441
|
+
description: rawLookup.get(position.post_id)?.description,
|
|
442
|
+
requirements: rawLookup.get(position.post_id)?.positionDemand,
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
const matches = shortlist.slice(0, topN).map((s) => {
|
|
446
|
+
const mr = s.reasons.length > 0
|
|
447
|
+
? s.reasons.slice(0, 5)
|
|
448
|
+
: ["no specific keyword overlap — surfaced from broad position pool"];
|
|
449
|
+
return {
|
|
450
|
+
...s.position,
|
|
451
|
+
description: s.description,
|
|
452
|
+
requirements: s.requirements,
|
|
453
|
+
match_reasons: mr,
|
|
454
|
+
};
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
ok: true,
|
|
458
|
+
source: "campus.kuaishou.cn",
|
|
459
|
+
extracted_terms: terms,
|
|
460
|
+
city_preferences: cities,
|
|
461
|
+
matches,
|
|
462
|
+
note: "match_reasons surfaces overlapping keywords, not a probability of getting an interview. " +
|
|
463
|
+
"The only authority on selection is HR.",
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
import { buildBespokeApplySchema as _buildBespokeApplySchema_kuaishou } from "./apply.js";
|
|
467
|
+
export async function fetchApplicationSchema(postId) {
|
|
468
|
+
const id = (postId ?? "").trim();
|
|
469
|
+
if (!id)
|
|
470
|
+
return { ok: false, source: "campus.kuaishou.cn", message: "post_id is required" };
|
|
471
|
+
let title = "";
|
|
472
|
+
let applyUrl = "https://campus.kuaishou.cn";
|
|
473
|
+
try {
|
|
474
|
+
const detail = (await fetchPositionDetail(id));
|
|
475
|
+
if (detail?.ok === false) {
|
|
476
|
+
return { ok: false, source: "campus.kuaishou.cn", message: detail.message ?? "post not found" };
|
|
477
|
+
}
|
|
478
|
+
title = detail?.title ?? "";
|
|
479
|
+
if (detail?.apply_url)
|
|
480
|
+
applyUrl = detail.apply_url;
|
|
481
|
+
}
|
|
482
|
+
catch { }
|
|
483
|
+
return {
|
|
484
|
+
ok: true,
|
|
485
|
+
schema: _buildBespokeApplySchema_kuaishou({
|
|
486
|
+
source: "campus.kuaishou.cn",
|
|
487
|
+
postId: id,
|
|
488
|
+
jobTitle: title,
|
|
489
|
+
applyUrl,
|
|
490
|
+
submitEndpoint: "https://campus.kuaishou.cn/recruit/campus/e/api/v1/apply/internship/apply",
|
|
491
|
+
submitKind: "multipart-session",
|
|
492
|
+
endpointVerified: true,
|
|
493
|
+
submitNotes: "Kuaishou — POST /recruit/campus/e/api/v1/apply/internship/apply with session cookie. Endpoint extracted from main.e3c87842.js bundle (the /recruit/campus/e/api/v1/ sub-tree has /apply/internship/apply, /apply/operate/apply, /apply/check, /apply/add/record, /resume/upload, /files/upload etc — all probe to 401 + {code:40008,message:\"user.not.login\"} = real REST routes). The /rest/campus-recruit/ prefix in earlier inference was wrong; the real backend mounts at /recruit/campus/e/api/v1/. Requires SSO login via /recruit/campus/e/login/cas.",
|
|
494
|
+
}),
|
|
495
|
+
};
|
|
496
|
+
}
|