@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.
Files changed (68) hide show
  1. package/dist/adapter.js +17 -0
  2. package/dist/agibot.js +399 -0
  3. package/dist/alibaba.js +509 -0
  4. package/dist/antgroup.js +397 -0
  5. package/dist/apply.js +1373 -0
  6. package/dist/baichuan.js +49 -0
  7. package/dist/baidu.js +452 -0
  8. package/dist/bilibili.js +455 -0
  9. package/dist/byd.js +412 -0
  10. package/dist/bytedance.js +619 -0
  11. package/dist/cainiao.js +56 -0
  12. package/dist/cambricon.js +33 -0
  13. package/dist/cdp.js +237 -0
  14. package/dist/cicc.js +56 -0
  15. package/dist/coverage.js +60 -0
  16. package/dist/deepseek.js +25 -0
  17. package/dist/didi.js +381 -0
  18. package/dist/feishu.js +577 -0
  19. package/dist/galaxyuniversal.js +24 -0
  20. package/dist/geely.js +35 -0
  21. package/dist/greenhouse.js +432 -0
  22. package/dist/hikvision.js +58 -0
  23. package/dist/horizonrobotics.js +46 -0
  24. package/dist/hoyoverse.js +26 -0
  25. package/dist/huawei.js +537 -0
  26. package/dist/iflytek.js +380 -0
  27. package/dist/index.js +1828 -0
  28. package/dist/iqiyi.js +494 -0
  29. package/dist/jd.js +559 -0
  30. package/dist/kuaishou.js +496 -0
  31. package/dist/lever.js +455 -0
  32. package/dist/liauto.js +393 -0
  33. package/dist/liepin.js +357 -0
  34. package/dist/lilith.js +300 -0
  35. package/dist/megvii.js +27 -0
  36. package/dist/meituan.js +633 -0
  37. package/dist/memory.js +76 -0
  38. package/dist/mihoyo.js +308 -0
  39. package/dist/minimax.js +32 -0
  40. package/dist/moka.js +473 -0
  41. package/dist/moonshot.js +24 -0
  42. package/dist/netease.js +424 -0
  43. package/dist/nio.js +24 -0
  44. package/dist/oppo.js +285 -0
  45. package/dist/pdd.js +614 -0
  46. package/dist/pingan.js +493 -0
  47. package/dist/sensetime.js +51 -0
  48. package/dist/sf.js +310 -0
  49. package/dist/stepfun.js +24 -0
  50. package/dist/tencent.js +770 -0
  51. package/dist/trip.js +396 -0
  52. package/dist/unitree.js +418 -0
  53. package/dist/vivo.js +361 -0
  54. package/dist/webank.js +55 -0
  55. package/dist/wecruit.js +438 -0
  56. package/dist/weibo.js +337 -0
  57. package/dist/weride.js +29 -0
  58. package/dist/xiaohongshu.js +480 -0
  59. package/dist/xiaomi.js +529 -0
  60. package/dist/xpeng.js +34 -0
  61. package/dist/zerooneai.js +42 -0
  62. package/dist/zhipu.js +478 -0
  63. package/extension/README.md +79 -0
  64. package/extension/background.js +177 -0
  65. package/extension/manifest.json +55 -0
  66. package/extension/popup.html +37 -0
  67. package/extension/popup.js +54 -0
  68. package/package.json +61 -0
package/dist/liauto.js ADDED
@@ -0,0 +1,393 @@
1
+ // Thin client for 理想汽车 (Li Auto) public recruiting API at www.lixiang.com.
2
+ //
3
+ // Both campus (校园/实习) and social-hire (社招) job feeds are backed by the
4
+ // same API server at api-web.lixiang.com. All endpoints are unauthenticated
5
+ // GET requests; POST with a JSON body returns HTTP 100012 "no access" for all
6
+ // public-facing paths.
7
+ //
8
+ // ============================================================
9
+ // Endpoint inventory (probed 2026-05, JS bundle employ/index.3f85ec75.js):
10
+ //
11
+ // GET https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit/school/job-page
12
+ // Required params: page=<int>, page_size=<int>
13
+ // Optional params: search=<string> — keyword filter on title (server-side)
14
+ // project_id=<int> — filter by recruit project (see taxonomy)
15
+ // Response: { code:0, message:"成功", data:{ page, page_size, total_pages,
16
+ // total_count, items:[...] } }
17
+ // Total campus + intern listings as of 2026-05: ~361 posts
18
+ //
19
+ // GET https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit/social/job-page
20
+ // Same params as school endpoint.
21
+ // Total social-hire listings: ~2185 posts
22
+ //
23
+ // GET https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit/job/detail
24
+ // Required params: job_id=<int>
25
+ // Response: { code:0, data:{ id, code, title, description, requirements,
26
+ // job_mode_name, job_mode, first_job_function_title,
27
+ // second_job_function_title, location_title, department_title,
28
+ // subject_name, limit_count, hire_mode, is_collect, is_apply, is_prior } }
29
+ //
30
+ // GET https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit/school/project/list
31
+ // No params required.
32
+ // Returns: { data:{ item:[{ id, name }] } }
33
+ // (Recruit-project taxonomy for campus posts.)
34
+ //
35
+ // GET https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit/school/job/function
36
+ // Returns: job-function category hierarchy for campus jobs.
37
+ //
38
+ // ============================================================
39
+ // Note: query params that have NO effect on school/job-page output (silently ignored):
40
+ // keyword, title, q, job_mode, job_mode_name, hire_mode,
41
+ // first_job_function_id, second_job_function_id, subject_id, location_title.
42
+ // Only `search` and `project_id` are effective server-side filters.
43
+ //
44
+ // ============================================================
45
+ // Recruit-project taxonomy (GET /recruit/school/project/list, probed 2026-05):
46
+ // id:13 2026"理想+" (~6 posts)
47
+ // id:12 2026校园招聘 (~125 posts)
48
+ // id:11 2025春招 (~3 posts)
49
+ // id:10 2024校园招聘 (~0 posts)
50
+ // id:9 2025秋招 (~1 post)
51
+ // id:5 实习生招聘 (~226 posts)
52
+ //
53
+ // ============================================================
54
+ // Job-function category hierarchy (GET /recruit/school/job/function, probed 2026-05):
55
+ // id:8 整车研发 (30 posts) — 底盘, 车身&内外饰, 热管理, 电池开发, 动力驱动,
56
+ // 增程系统, 整车集成, 研发质量, 虚拟开发与验证,
57
+ // 制造工程, 材料开发, 座舱, 空气动力, 车载硬件,
58
+ // 电子电器架构, 硬件测试, 工业化
59
+ // id:1 算法与软件 (71 posts) — 算法, 软件测试, 技术运维, 信息安全, 车辆控制,
60
+ // 前端开发, 后端开发, 操作系统及嵌入式开发,
61
+ // 数据开发, 数据分析
62
+ // id:92 芯片研发 (4 posts) — 芯片前端设计, 芯片后端设计, 软件设计, 芯片架构
63
+ // id:21 产品 (4 posts) — 软件产品, 硬件产品, 产品运营
64
+ // id:29 供应链与智能制造 (2 posts) — 质量安全, 采购与供应计划, 制造
65
+ // id:34 市场与销售服务 (70 posts) — 储备管理, 零售, 交付, 质量运营, 售后运营,
66
+ // 商业拓展, 销售规划与运营
67
+ //
68
+ // ============================================================
69
+ // Job-mode codes (job_mode field in item responses):
70
+ // "201" = 正式 (new-grad / full-time campus hire)
71
+ // "202" = 实习 (intern)
72
+ // "" = 全职 (social-hire full-time)
73
+ //
74
+ // ============================================================
75
+ // Detail page URL: https://www.lixiang.com/job/detail/<id>.html
76
+ // Campus listing: https://www.lixiang.com/employ/campus.html
77
+ // Social listing: https://www.lixiang.com/employ/social.html
78
+ // Employ home: https://www.lixiang.com/employ.html
79
+ //
80
+ // ---- PositionSummary field mapping (Li Auto → canonical) ----
81
+ // post_id ← String(item.id)
82
+ // title ← item.title
83
+ // project ← item.first_job_function_title ?? item.second_job_function_title
84
+ // (function category, closest equivalent to Tencent's projectName)
85
+ // recruit_label ← item.job_mode_name (e.g. "实习" / "正式" / "全职")
86
+ // bgs ← "" (Li Auto does not expose a BG/事业群 field in the public API)
87
+ // work_cities ← item.location_title
88
+ // apply_url ← https://www.lixiang.com/job/detail/<id>.html
89
+ import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
90
+ export { checkResume };
91
+ const API_ROOT = "https://api-web.lixiang.com/osd-hr-recruitment-website/v1/recruit";
92
+ const EMPLOY_PAGE = "https://www.lixiang.com/employ.html";
93
+ const CAMPUS_PAGE = "https://www.lixiang.com/employ/campus.html";
94
+ const SOCIAL_PAGE = "https://www.lixiang.com/employ/social.html";
95
+ const DETAIL_PAGE = (id) => `https://www.lixiang.com/job/detail/${encodeURIComponent(String(id))}.html`;
96
+ const DEFAULT_HEADERS = {
97
+ "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",
98
+ Accept: "application/json, text/plain, */*",
99
+ Referer: EMPLOY_PAGE,
100
+ };
101
+ async function get(path, params, referer) {
102
+ const qs = Object.entries(params)
103
+ .filter(([, v]) => v !== undefined && v !== "")
104
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
105
+ .join("&");
106
+ const url = `${API_ROOT}${path}${qs ? "?" + qs : ""}`;
107
+ let response;
108
+ try {
109
+ response = await fetch(url, {
110
+ headers: { ...DEFAULT_HEADERS, ...(referer ? { Referer: referer } : {}) },
111
+ });
112
+ }
113
+ catch (err) {
114
+ return {
115
+ ok: false,
116
+ message: `network error: ${err instanceof Error ? err.message : String(err)}`,
117
+ };
118
+ }
119
+ if (!response.ok) {
120
+ return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
121
+ }
122
+ let payload;
123
+ try {
124
+ payload = (await response.json());
125
+ }
126
+ catch (err) {
127
+ return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : err}` };
128
+ }
129
+ return {
130
+ ok: payload.code === 0,
131
+ data: payload.data,
132
+ message: payload.message || (payload.code === 0 ? "ok" : "upstream error"),
133
+ };
134
+ }
135
+ function summarizePosition(item) {
136
+ const id = String(item.id ?? "");
137
+ const project = (item.first_job_function_title ?? item.second_job_function_title ?? "").trim();
138
+ return {
139
+ post_id: id,
140
+ title: item.title ?? "",
141
+ project,
142
+ recruit_label: item.job_mode_name ?? "",
143
+ bgs: "",
144
+ work_cities: item.location_title ?? "",
145
+ apply_url: id ? DETAIL_PAGE(id) : EMPLOY_PAGE,
146
+ };
147
+ }
148
+ // ---------- searchPositions ----------
149
+ export async function searchPositions(opts = {}) {
150
+ const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 20));
151
+ const page = Math.max(1, opts.page ?? 1);
152
+ const keyword = (opts.keyword ?? "").trim().slice(0, 60);
153
+ const campusOnly = opts.campusOnly !== false; // default true
154
+ const path = campusOnly ? "/school/job-page" : "/social/job-page";
155
+ const referer = campusOnly ? CAMPUS_PAGE : SOCIAL_PAGE;
156
+ const params = {
157
+ page,
158
+ page_size: pageSize,
159
+ };
160
+ if (keyword)
161
+ params.search = keyword;
162
+ if (campusOnly && opts.projectId !== undefined)
163
+ params.project_id = opts.projectId;
164
+ const response = await get(path, params, referer);
165
+ if (!response.ok || !response.data) {
166
+ return {
167
+ ok: false,
168
+ message: response.message,
169
+ source: "www.lixiang.com",
170
+ query: params,
171
+ positions: [],
172
+ };
173
+ }
174
+ const rows = response.data.items ?? [];
175
+ return {
176
+ ok: true,
177
+ source: "www.lixiang.com",
178
+ query: params,
179
+ page,
180
+ page_size: pageSize,
181
+ total: response.data.total_count ?? rows.length,
182
+ positions: rows.map(summarizePosition),
183
+ };
184
+ }
185
+ // ---------- fetchAllPositions ----------
186
+ export async function fetchAllPositions(opts = {}) {
187
+ const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 100));
188
+ const maxPages = Math.max(1, opts.maxPages ?? 10);
189
+ const bucket = [];
190
+ let total;
191
+ for (let page = 1; page <= maxPages; page++) {
192
+ const result = await searchPositions({ ...opts, page, pageSize });
193
+ if (!result.ok) {
194
+ return {
195
+ ok: false,
196
+ message: result.message,
197
+ source: "www.lixiang.com",
198
+ fetched: bucket.length,
199
+ positions: bucket,
200
+ };
201
+ }
202
+ if (total === undefined)
203
+ total = result.total;
204
+ if (!result.positions.length)
205
+ break;
206
+ bucket.push(...result.positions);
207
+ if (total !== undefined && bucket.length >= total)
208
+ break;
209
+ }
210
+ return {
211
+ ok: true,
212
+ source: "www.lixiang.com",
213
+ total: total ?? bucket.length,
214
+ fetched: bucket.length,
215
+ positions: bucket,
216
+ };
217
+ }
218
+ // ---------- fetchPositionDetail ----------
219
+ export async function fetchPositionDetail(postId) {
220
+ const id = (postId ?? "").trim();
221
+ if (!id)
222
+ return { ok: false, source: "www.lixiang.com", message: "post_id is required" };
223
+ const response = await get("/job/detail", { job_id: id }, EMPLOY_PAGE);
224
+ if (!response.ok || !response.data) {
225
+ return {
226
+ ok: false,
227
+ source: "www.lixiang.com",
228
+ post_id: id,
229
+ message: response.message || "no detail returned",
230
+ };
231
+ }
232
+ const raw = response.data;
233
+ return {
234
+ ok: true,
235
+ source: "www.lixiang.com",
236
+ post_id: String(raw.id ?? id),
237
+ code: raw.code ?? "",
238
+ title: raw.title ?? "",
239
+ job_mode: raw.job_mode ?? "",
240
+ recruit_label: raw.job_mode_name ?? "",
241
+ first_job_function: raw.first_job_function_title ?? "",
242
+ second_job_function: raw.second_job_function_title ?? "",
243
+ department: raw.department_title ?? "",
244
+ work_cities: raw.location_title ?? "",
245
+ subject: raw.subject_name ?? "",
246
+ description_html: raw.description ?? "",
247
+ requirements_html: raw.requirements ?? "",
248
+ limit_count: raw.limit_count ?? "",
249
+ apply_url: DETAIL_PAGE(raw.id ?? id),
250
+ };
251
+ }
252
+ export async function fetchDictionaries() {
253
+ const [projects, functions] = await Promise.all([
254
+ get("/school/project/list", {}, CAMPUS_PAGE),
255
+ get("/school/job/function", {}, CAMPUS_PAGE),
256
+ ]);
257
+ const recruitProjects = (projects.data?.item ?? []).map((p) => ({
258
+ id: p.id ?? 0,
259
+ name: p.name ?? "",
260
+ }));
261
+ const jobFunctions = (functions.data?.list ?? []).map((cat) => ({
262
+ id: cat.id ?? 0,
263
+ title: cat.title ?? "",
264
+ job_count: cat.job_count ?? 0,
265
+ description: cat.description ?? "",
266
+ sub_functions: (cat.list ?? []).map((s) => ({
267
+ id: s.id ?? 0,
268
+ title: s.title ?? "",
269
+ })),
270
+ }));
271
+ return {
272
+ ok: projects.ok && functions.ok,
273
+ source: "www.lixiang.com",
274
+ api_host: "api-web.lixiang.com",
275
+ verified_at: new Date().toISOString(),
276
+ campus_page: CAMPUS_PAGE,
277
+ social_page: SOCIAL_PAGE,
278
+ recruit_projects: recruitProjects,
279
+ job_functions: jobFunctions,
280
+ job_mode_codes: [
281
+ { code: "201", label: "正式", note: "campus new-grad full-time" },
282
+ { code: "202", label: "实习", note: "intern (campus)" },
283
+ { code: "", label: "全职", note: "social-hire full-time" },
284
+ ],
285
+ note: "Filter params: use `search` for keyword, `project_id` for recruit project (campus only). " +
286
+ "Campus endpoint: ~361 total (201=正式, 202=实习 mixed). " +
287
+ "Social endpoint: ~2185 total.",
288
+ };
289
+ }
290
+ // ---------- notices (no public endpoint) ----------
291
+ const STUB_NOTICE = {
292
+ ok: false,
293
+ source: "www.lixiang.com",
294
+ message: "Li Auto: no public notices/announcements endpoint",
295
+ };
296
+ export async function listNotices() {
297
+ return STUB_NOTICE;
298
+ }
299
+ export async function getNotice(_id) {
300
+ return { ok: false, source: "www.lixiang.com", message: "Li Auto: no public notices endpoint" };
301
+ }
302
+ export async function findNoticesByQuestion(_question, _opts = {}) {
303
+ return { ok: false, source: "www.lixiang.com", message: "Li Auto: no public notices endpoint" };
304
+ }
305
+ // ---------- matchResume ----------
306
+ export async function matchResume(text, opts = {}) {
307
+ const topN = Math.max(1, opts.topN ?? 5);
308
+ const candidates = Math.max(topN, opts.candidates ?? 30);
309
+ const { terms, cities } = extractResumeSignals(text ?? "");
310
+ if (!terms.length) {
311
+ return {
312
+ ok: false,
313
+ source: "www.lixiang.com",
314
+ message: "could not extract any technical signals from the text",
315
+ preview: (text ?? "").slice(0, 120),
316
+ };
317
+ }
318
+ const keyword = terms.slice(0, 3).join(" ");
319
+ // Fetch campus + intern listings with keyword filter
320
+ const list = await searchPositions({ keyword, page: 1, pageSize: 100, campusOnly: true });
321
+ if (!list.ok) {
322
+ return {
323
+ ok: false,
324
+ source: "www.lixiang.com",
325
+ message: list.message,
326
+ positions: [],
327
+ };
328
+ }
329
+ const scored = [];
330
+ for (const p of list.positions) {
331
+ const blob = [p.title, p.project, p.recruit_label, p.work_cities].join(" ");
332
+ const { score, reasons } = scoreOverlap(blob, terms, cities);
333
+ if (score > 0)
334
+ scored.push({ score, position: p, reasons });
335
+ }
336
+ scored.sort((a, b) => b.score - a.score);
337
+ let shortlist = scored.slice(0, Math.max(topN, candidates));
338
+ if (!shortlist.length) {
339
+ // Fall back: return the first N positions regardless of score
340
+ shortlist = list.positions.slice(0, candidates).map((position) => ({
341
+ score: 0,
342
+ position,
343
+ reasons: [],
344
+ }));
345
+ }
346
+ const matches = shortlist.slice(0, topN).map((s) => {
347
+ const mr = s.reasons.length > 0
348
+ ? s.reasons.slice(0, 5)
349
+ : ["no specific keyword overlap — surfaced from initial keyword search"];
350
+ return { ...s.position, match_reasons: mr };
351
+ });
352
+ return {
353
+ ok: true,
354
+ source: "www.lixiang.com",
355
+ extracted_terms: terms,
356
+ city_preferences: cities,
357
+ keyword_used: keyword,
358
+ matches,
359
+ note: "match_reasons surfaces overlapping keywords, not a probability of getting an interview. " +
360
+ "The only authority on selection is HR.",
361
+ };
362
+ }
363
+ import { buildBespokeApplySchema as _buildBespokeApplySchema_liauto } from "./apply.js";
364
+ export async function fetchApplicationSchema(postId) {
365
+ const id = (postId ?? "").trim();
366
+ if (!id)
367
+ return { ok: false, source: "www.lixiang.com", message: "post_id is required" };
368
+ let title = "";
369
+ let applyUrl = "https://www.lixiang.com";
370
+ try {
371
+ const detail = (await fetchPositionDetail(id));
372
+ if (detail?.ok === false) {
373
+ return { ok: false, source: "www.lixiang.com", message: detail.message ?? "post not found" };
374
+ }
375
+ title = detail?.title ?? "";
376
+ if (detail?.apply_url)
377
+ applyUrl = detail.apply_url;
378
+ }
379
+ catch { }
380
+ return {
381
+ ok: true,
382
+ schema: _buildBespokeApplySchema_liauto({
383
+ source: "www.lixiang.com",
384
+ postId: id,
385
+ jobTitle: title,
386
+ applyUrl,
387
+ submitEndpoint: "https://www.lixiang.com/api/career/apply",
388
+ submitKind: "multipart-session",
389
+ endpointVerified: true,
390
+ submitNotes: "Li Auto — POST /api/career/apply with session cookie. Endpoint anon-probed → {code: 2, msg: \"请在配置文件配置可访问域名\"} (real backend, domain ACL needs Origin/Referer headers). Body shape still needs validation.",
391
+ }),
392
+ };
393
+ }