@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/weibo.js ADDED
@@ -0,0 +1,337 @@
1
+ // Weibo / Sina campus + social recruiting adapter.
2
+ //
3
+ // ============================================================
4
+ // API DISCOVERY (probed 2026-05-15)
5
+ //
6
+ // Weibo/Sina posts every position through their Moka (北森's competitor)
7
+ // recruitment portal at app.mokahr.com under the `sina` tenant. The original
8
+ // career.sina.com.cn 302-loop was a red herring — that host just redirects
9
+ // into the Moka SPA at:
10
+ //
11
+ // campus: https://app.mokahr.com/campus-recruitment/sina/43536
12
+ // social: https://app.mokahr.com/social-recruitment/sina/43535
13
+ //
14
+ // Moka exposes a fully anonymous JSON endpoint for the position list:
15
+ //
16
+ // POST https://app.mokahr.com/api/outer/ats-apply/website/jobs/v2
17
+ //
18
+ // Required body fields: `orgId` ("sina"), `siteId` (the trailing site id from
19
+ // the URL — 43536 campus, 43535 social), plus pagination/keyword. The response
20
+ // body is AES-128-CBC encrypted:
21
+ //
22
+ // {
23
+ // "data": <base64 ciphertext>,
24
+ // "necromancer": <hex string AES key>
25
+ // }
26
+ //
27
+ // Decryption parameters:
28
+ // key = utf-8 bytes of `necromancer` (per-response, 16 chars / 16 bytes)
29
+ // iv = utf-8 bytes of a static `aesIv` embedded in the SPA page HTML
30
+ // (`window.TurboApply.data.aesIv`). For the sina tenant the iv is
31
+ // "de7c21ed8d6f50fe" and has remained stable across page reloads.
32
+ // mode = CBC, padding = PKCS#7
33
+ //
34
+ // Endpoint inventory (all anon, all app.mokahr.com):
35
+ // POST /api/outer/ats-apply/website/jobs/v2 → paginated list
36
+ // POST /api/outer/ats-apply/website/group-by-job → grouped list
37
+ // POST /api/outer/ats-apply/website/job → single posting
38
+ // POST /api/outer/ats-apply/website/jobs/v2/filterFieldsAggregations
39
+ // → filter taxonomy
40
+ // POST /api/outer/ats-apply/website/manage-job-count → counts only
41
+ // POST /api/outer/ats-apply/privacy-policy/get → site privacy
42
+ // ============================================================
43
+ import { createDecipheriv } from "node:crypto";
44
+ import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
45
+ export { checkResume };
46
+ const SOURCE = "app.mokahr.com/sina";
47
+ const API_ROOT = "https://app.mokahr.com";
48
+ const ORG_ID = "sina";
49
+ const CAMPUS_SITE_ID = 43536;
50
+ const SOCIAL_SITE_ID = 43535;
51
+ const CAMPUS_PAGE = `https://app.mokahr.com/campus-recruitment/sina/${CAMPUS_SITE_ID}`;
52
+ const SOCIAL_PAGE = `https://app.mokahr.com/social-recruitment/sina/${SOCIAL_SITE_ID}`;
53
+ // AES IV embedded in `window.TurboApply.data.aesIv` of the sina recruitment SPA.
54
+ const AES_IV = "de7c21ed8d6f50fe";
55
+ const DEFAULT_HEADERS = {
56
+ "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",
57
+ Accept: "application/json, text/plain, */*",
58
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
59
+ "Content-Type": "application/json",
60
+ Referer: CAMPUS_PAGE,
61
+ Origin: API_ROOT,
62
+ };
63
+ function decryptResponse(b64Cipher, hexKey) {
64
+ const cipherBuf = Buffer.from(b64Cipher, "base64");
65
+ const key = Buffer.from(hexKey, "utf-8");
66
+ const iv = Buffer.from(AES_IV, "utf-8");
67
+ const decipher = createDecipheriv("aes-128-cbc", key, iv);
68
+ const plain = Buffer.concat([decipher.update(cipherBuf), decipher.final()]);
69
+ return JSON.parse(plain.toString("utf-8"));
70
+ }
71
+ async function post(path, body, referer = CAMPUS_PAGE) {
72
+ let response;
73
+ try {
74
+ response = await fetch(`${API_ROOT}${path}`, {
75
+ method: "POST",
76
+ headers: { ...DEFAULT_HEADERS, Referer: referer },
77
+ body: JSON.stringify(body),
78
+ });
79
+ }
80
+ catch (err) {
81
+ return {
82
+ ok: false,
83
+ message: `network error: ${err instanceof Error ? err.message : String(err)}`,
84
+ };
85
+ }
86
+ if (!response.ok) {
87
+ return { ok: false, message: `HTTP ${response.status}: ${response.statusText}` };
88
+ }
89
+ let env;
90
+ try {
91
+ env = (await response.json());
92
+ }
93
+ catch (err) {
94
+ return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : err}` };
95
+ }
96
+ // Error envelope (no ciphertext): code != 0.
97
+ if (env.code !== undefined && (!env.data || typeof env.data !== "string")) {
98
+ return { ok: false, message: env.msg || `moka error code=${env.code}` };
99
+ }
100
+ if (!env.data || !env.necromancer) {
101
+ return { ok: false, message: "missing ciphertext or key in moka response" };
102
+ }
103
+ let plain;
104
+ try {
105
+ plain = decryptResponse(env.data, env.necromancer);
106
+ }
107
+ catch (err) {
108
+ return {
109
+ ok: false,
110
+ message: `decrypt failed: ${err instanceof Error ? err.message : String(err)}`,
111
+ };
112
+ }
113
+ if (!plain.success || plain.code !== 0) {
114
+ return { ok: false, message: plain.msg || `moka inner code=${plain.code}` };
115
+ }
116
+ return { ok: true, data: plain.data, message: plain.msg || "ok" };
117
+ }
118
+ function summarize(item, channel, siteId) {
119
+ const id = String(item.id ?? "");
120
+ const cities = (item.locations ?? [])
121
+ .map((l) => [l.provinceName, l.cityName].filter(Boolean).join("·"))
122
+ .filter((s) => s.length > 0)
123
+ .join(", ");
124
+ const label = channel === "social" ? "社招" : item.hireMode === 2 ? "校招" : "校招";
125
+ return {
126
+ post_id: id,
127
+ title: (item.title ?? "").trim(),
128
+ project: item.projectFolder?.name?.trim() ?? "",
129
+ recruit_label: label,
130
+ bgs: (item.department?.name ?? "").trim(),
131
+ work_cities: cities,
132
+ apply_url: id
133
+ ? `https://app.mokahr.com/${channel}-recruitment/sina/${siteId}/job/${encodeURIComponent(id)}`
134
+ : channel === "social"
135
+ ? SOCIAL_PAGE
136
+ : CAMPUS_PAGE,
137
+ };
138
+ }
139
+ // ---------- searchPositions ----------
140
+ export async function searchPositions(opts = {}) {
141
+ const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 20));
142
+ const page = Math.max(1, opts.page ?? 1);
143
+ const channel = opts.channel ?? "campus";
144
+ const siteId = channel === "social" ? SOCIAL_SITE_ID : CAMPUS_SITE_ID;
145
+ const refererPage = channel === "social" ? SOCIAL_PAGE : CAMPUS_PAGE;
146
+ const body = {
147
+ orgId: ORG_ID,
148
+ siteId: String(siteId),
149
+ limit: pageSize,
150
+ offset: (page - 1) * pageSize,
151
+ needStat: true,
152
+ jobIdTopList: [],
153
+ customFields: {},
154
+ site: channel,
155
+ locale: "zh-CN",
156
+ };
157
+ if (opts.keyword)
158
+ body.keyword = opts.keyword.trim().slice(0, 60);
159
+ const r = await post("/api/outer/ats-apply/website/jobs/v2", body, refererPage);
160
+ if (!r.ok || !r.data) {
161
+ return {
162
+ ok: false,
163
+ source: SOURCE,
164
+ message: r.message,
165
+ query: body,
166
+ positions: [],
167
+ };
168
+ }
169
+ const rows = r.data.jobs ?? [];
170
+ return {
171
+ ok: true,
172
+ source: SOURCE,
173
+ query: body,
174
+ page,
175
+ page_size: pageSize,
176
+ total: r.data.jobStats?.total ?? rows.length,
177
+ positions: rows.map((j) => summarize(j, channel, siteId)),
178
+ };
179
+ }
180
+ // ---------- fetchAllPositions ----------
181
+ export async function fetchAllPositions(opts = {}) {
182
+ const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 50));
183
+ const maxPages = Math.max(1, opts.maxPages ?? 20);
184
+ const bucket = [];
185
+ let total;
186
+ for (let page = 1; page <= maxPages; page++) {
187
+ const r = await searchPositions({
188
+ keyword: opts.keyword,
189
+ page,
190
+ pageSize,
191
+ channel: opts.channel,
192
+ });
193
+ if (!r.ok) {
194
+ return {
195
+ ok: false,
196
+ source: SOURCE,
197
+ message: r.message,
198
+ total: 0,
199
+ fetched: bucket.length,
200
+ positions: bucket,
201
+ };
202
+ }
203
+ if (total === undefined)
204
+ total = r.total;
205
+ if (!r.positions.length)
206
+ break;
207
+ bucket.push(...r.positions);
208
+ if (total !== undefined && bucket.length >= total)
209
+ break;
210
+ }
211
+ return {
212
+ ok: true,
213
+ source: SOURCE,
214
+ total: total ?? bucket.length,
215
+ fetched: bucket.length,
216
+ positions: bucket,
217
+ };
218
+ }
219
+ export async function fetchPositionDetail(postId) {
220
+ const id = (postId ?? "").trim();
221
+ if (!id)
222
+ return { ok: false, source: SOURCE, message: "post_id is required", post_id: id };
223
+ const r = await post("/api/outer/ats-apply/website/job", {
224
+ orgId: ORG_ID,
225
+ siteId: CAMPUS_SITE_ID,
226
+ jobId: id,
227
+ });
228
+ if (!r.ok || !r.data) {
229
+ return { ok: false, source: SOURCE, message: r.message || "no detail returned", post_id: id };
230
+ }
231
+ const raw = r.data;
232
+ const cities = (raw.locations ?? [])
233
+ .map((l) => [l.provinceName, l.cityName].filter(Boolean).join("·"))
234
+ .join(", ");
235
+ return {
236
+ ok: true,
237
+ source: SOURCE,
238
+ post_id: String(raw.id ?? id),
239
+ title: raw.title ?? "",
240
+ project: raw.projectFolder?.name ?? "",
241
+ department: raw.department?.name ?? "",
242
+ description: (raw.description ?? raw.responsibility ?? "").trim(),
243
+ requirements: (raw.requirement ?? "").trim(),
244
+ work_cities: cities,
245
+ commitment: raw.commitment ?? "",
246
+ published_at: raw.publishedAt ?? raw.openedAt ?? "",
247
+ apply_url: `https://app.mokahr.com/campus-recruitment/sina/${CAMPUS_SITE_ID}/job/${encodeURIComponent(String(raw.id ?? id))}`,
248
+ };
249
+ }
250
+ // ---------- fetchDictionaries ----------
251
+ export async function fetchDictionaries() {
252
+ const r = await post("/api/outer/ats-apply/website/jobs/v2/filterFieldsAggregations", { orgId: ORG_ID, siteId: CAMPUS_SITE_ID });
253
+ return {
254
+ ok: r.ok,
255
+ source: SOURCE,
256
+ api_host: API_ROOT,
257
+ verified_at: new Date().toISOString(),
258
+ filter_fields: r.data ?? null,
259
+ channels: { campus: CAMPUS_SITE_ID, social: SOCIAL_SITE_ID },
260
+ };
261
+ }
262
+ // ---------- notices (no public endpoint on Moka tenant) ----------
263
+ const NO_NOTICES = "Weibo/Sina Moka tenant does not expose a public notices/announcements endpoint.";
264
+ export async function listNotices() {
265
+ return { ok: false, source: SOURCE, message: NO_NOTICES, notices: [] };
266
+ }
267
+ export async function getNotice(noticeId) {
268
+ return { ok: false, source: SOURCE, message: NO_NOTICES, notice_id: noticeId };
269
+ }
270
+ export async function findNoticesByQuestion(question, _opts = {}) {
271
+ return { ok: false, source: SOURCE, question, message: NO_NOTICES, matches: [] };
272
+ }
273
+ // ---------- matchResume ----------
274
+ export async function matchResume(text, opts = {}) {
275
+ const { terms, cities } = extractResumeSignals(text ?? "");
276
+ const topN = Math.max(1, opts.topN ?? 5);
277
+ const candidates = Math.max(topN, opts.candidates ?? 200);
278
+ const all = await fetchAllPositions({ pageSize: 50, maxPages: Math.ceil(candidates / 50) });
279
+ if (!all.ok) {
280
+ return {
281
+ ok: false,
282
+ source: SOURCE,
283
+ message: all.message,
284
+ extracted_terms: terms,
285
+ city_preferences: cities,
286
+ matches: [],
287
+ };
288
+ }
289
+ const scored = [];
290
+ for (const p of all.positions) {
291
+ const haystack = `${p.title} ${p.project} ${p.bgs} ${p.work_cities}`;
292
+ const score = scoreOverlap(haystack, terms, cities).score;
293
+ if (score > 0)
294
+ scored.push({ score, position: p });
295
+ }
296
+ scored.sort((a, b) => b.score - a.score);
297
+ return {
298
+ ok: true,
299
+ source: SOURCE,
300
+ extracted_terms: terms,
301
+ city_preferences: cities,
302
+ candidate_pool: all.positions.length,
303
+ matches: scored.slice(0, topN).map((s) => s.position),
304
+ };
305
+ }
306
+ export { extractResumeSignals, scoreOverlap };
307
+ import { buildBespokeApplySchema as _buildBespokeApplySchema_weibo } from "./apply.js";
308
+ export async function fetchApplicationSchema(postId) {
309
+ const id = (postId ?? "").trim();
310
+ if (!id)
311
+ return { ok: false, source: "career.sina.com.cn", message: "post_id is required" };
312
+ let title = "";
313
+ let applyUrl = "https://career.sina.com.cn";
314
+ try {
315
+ const detail = (await fetchPositionDetail(id));
316
+ if (detail?.ok === false) {
317
+ return { ok: false, source: "career.sina.com.cn", message: detail.message ?? "post not found" };
318
+ }
319
+ title = detail?.title ?? "";
320
+ if (detail?.apply_url)
321
+ applyUrl = detail.apply_url;
322
+ }
323
+ catch { }
324
+ return {
325
+ ok: true,
326
+ schema: _buildBespokeApplySchema_weibo({
327
+ source: "career.sina.com.cn",
328
+ postId: id,
329
+ jobTitle: title,
330
+ applyUrl,
331
+ submitEndpoint: "https://app.mokahr.com/api/outer/ats-apply/website/apply",
332
+ submitKind: "moka-aes",
333
+ endpointVerified: true,
334
+ submitNotes: "Weibo (Sina careers) — POST /api/outer/ats-apply/website/apply on app.mokahr.com (career.sina.com.cn proxies to Moka under tenant `sina`; read endpoints already at app.mokahr.com). Same Moka apply route as the other 7 Moka adapters (verified via AES envelope probe in 1.0.39). Session captured at career.sina.com.cn flows through to Moka. Body shape still needs validation.",
335
+ }),
336
+ };
337
+ }
package/dist/weride.js ADDED
@@ -0,0 +1,29 @@
1
+ // Thin wrapper for 文远知行 (WeRide) careers, hosted on Lever.
2
+ //
3
+ // ============================================================
4
+ // Discovery notes (probed 2026-05):
5
+ //
6
+ // Attempted endpoints:
7
+ // https://career.weride.ai — no public unauthenticated job API
8
+ // https://weride.jobs.feishu.cn — HTTP 400 (no Feishu portal)
9
+ // https://weride.app.mokahr.com — no Moka tenant
10
+ //
11
+ // Live endpoint: https://api.lever.co/v0/postings/weride?mode=json
12
+ // Lever slug: weride
13
+ // Total positions: ~34 (probed 2026-05) — San Jose / Sunnyvale / Guangzhou
14
+ // autonomous-driving, perception, planning, robotics.
15
+ //
16
+ // The Lever board includes both US and Guangzhou postings, scoped client-side
17
+ // by the `cities` filter once the location field is populated upstream.
18
+ import { createAdapter } from "./lever.js";
19
+ const adapter = createAdapter({ slug: "weride", label: "WeRide" });
20
+ export const searchPositions = adapter.searchPositions;
21
+ export const fetchAllPositions = adapter.fetchAllPositions;
22
+ export const fetchPositionDetail = adapter.fetchPositionDetail;
23
+ export const fetchDictionaries = adapter.fetchDictionaries;
24
+ export const listNotices = adapter.listNotices;
25
+ export const getNotice = adapter.getNotice;
26
+ export const findNoticesByQuestion = adapter.findNoticesByQuestion;
27
+ export const matchResume = adapter.matchResume;
28
+ export const checkResume = adapter.checkResume;
29
+ export const fetchApplicationSchema = adapter.fetchApplicationSchema;