@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
@@ -0,0 +1,33 @@
1
+ // 寒武纪 (Cambricon) careers adapter — Moka SSR + AES-128-CBC pagination.
2
+ //
3
+ // ============================================================
4
+ // API DISCOVERY (probed 2026-05-16)
5
+ //
6
+ // www.cambricon.com embeds links to Moka tenant URLs in its 加入我们 section:
7
+ //
8
+ // /campus-recruitment/cambricon/44201 ← campus + intern (main entry)
9
+ // /recommendation-recruitment/cambricon/42452 (referral channel, overlaps)
10
+ // /recommendation-recruitment/cambricon/46261 (referral channel, overlaps)
11
+ //
12
+ // No /social-recruitment/cambricon/<siteId> URL is published — Cambricon
13
+ // only opens 校招 / 实习 publicly through Moka. Same factory as
14
+ // `cli/src/moka.ts` (used by megvii / geely / etc.).
15
+ import { createAdapter } from "./moka.js";
16
+ const adapter = createAdapter({
17
+ orgSlug: "cambricon",
18
+ label: "Cambricon",
19
+ channels: [
20
+ { siteId: 44201, kind: "campus-recruitment", recruitType: "campus" },
21
+ ],
22
+ defaultRecruitType: "campus",
23
+ });
24
+ export const searchPositions = adapter.searchPositions;
25
+ export const fetchAllPositions = adapter.fetchAllPositions;
26
+ export const fetchPositionDetail = adapter.fetchPositionDetail;
27
+ export const fetchDictionaries = adapter.fetchDictionaries;
28
+ export const listNotices = adapter.listNotices;
29
+ export const getNotice = adapter.getNotice;
30
+ export const findNoticesByQuestion = adapter.findNoticesByQuestion;
31
+ export const matchResume = adapter.matchResume;
32
+ export const checkResume = adapter.checkResume;
33
+ export const fetchApplicationSchema = adapter.fetchApplicationSchema;
package/dist/cdp.js ADDED
@@ -0,0 +1,237 @@
1
+ // Headless-browser helper for adapters whose upstream is gated by anti-bot
2
+ // signatures that the CLI can't reproduce from raw HTTP.
3
+ //
4
+ // Usage pattern:
5
+ // 1. `await getBrowser()` returns a process-singleton puppeteer-core Browser
6
+ // attached to the user's system Chrome.
7
+ // 2. Call `viaBrowser(url, async page => …)` to navigate and run a fn in
8
+ // the page context, then receive the return value.
9
+ //
10
+ // Why puppeteer-core (not puppeteer): we attach to the user's existing
11
+ // Chrome installation; no 100MB Chromium download. Trade-off: we need a
12
+ // working Chrome executable path.
13
+ //
14
+ // Failure modes:
15
+ // * puppeteer-core not installed → ENOENT on dynamic import → caller
16
+ // receives `{ ok:false, reason:"puppeteer-not-installed", message: … }`
17
+ // and renders it as the canonical ok:false stub.
18
+ // * No Chrome found at any well-known path → same error shape with
19
+ // `reason:"chrome-not-found"`.
20
+ // * Browser launch failed (sandbox, profile lock, …) → `reason:"launch-failed"`.
21
+ import { existsSync } from "node:fs";
22
+ const CHROME_PATHS = [
23
+ // macOS
24
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
25
+ "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
26
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
27
+ // Linux
28
+ "/usr/bin/google-chrome",
29
+ "/usr/bin/google-chrome-stable",
30
+ "/usr/bin/chromium",
31
+ "/usr/bin/chromium-browser",
32
+ // Windows (when running under WSL / Git Bash)
33
+ "/c/Program Files/Google/Chrome/Application/chrome.exe",
34
+ ];
35
+ const 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";
36
+ // ---------- singleton browser ----------
37
+ let _browser = null;
38
+ let _browserError = null;
39
+ let _launching = null;
40
+ async function loadPuppeteer() {
41
+ try {
42
+ // Dynamic import; if puppeteer-core was tree-shaken or uninstalled,
43
+ // this rejects with ERR_MODULE_NOT_FOUND.
44
+ const mod = (await import("puppeteer-core"));
45
+ return { ok: true, mod: mod.default };
46
+ }
47
+ catch (err) {
48
+ return {
49
+ ok: false,
50
+ error: {
51
+ reason: "puppeteer-not-installed",
52
+ message: "`puppeteer-core` is not installed. Install it locally with " +
53
+ "`npm i puppeteer-core` (or `pnpm add puppeteer-core`). " +
54
+ `Original error: ${err instanceof Error ? err.message : String(err)}`,
55
+ },
56
+ };
57
+ }
58
+ }
59
+ function findChrome() {
60
+ if (process.env.JOB_PRO_CHROME && existsSync(process.env.JOB_PRO_CHROME)) {
61
+ return process.env.JOB_PRO_CHROME;
62
+ }
63
+ for (const p of CHROME_PATHS) {
64
+ if (existsSync(p))
65
+ return p;
66
+ }
67
+ return null;
68
+ }
69
+ async function launchOnce() {
70
+ const pp = await loadPuppeteer();
71
+ if (!pp.ok)
72
+ return pp.error;
73
+ const chrome = findChrome();
74
+ if (!chrome) {
75
+ return {
76
+ reason: "chrome-not-found",
77
+ message: "No Chrome/Chromium executable found. Tried: " +
78
+ CHROME_PATHS.join(", ") +
79
+ ". Set $JOB_PRO_CHROME=/path/to/chrome to override.",
80
+ };
81
+ }
82
+ // Optional egress proxy — useful for geo-fenced upstreams (e.g. hikvision
83
+ // requires a CN-egress to pass its Tencent EdgeOne 403 check). Set
84
+ // `$JOB_PRO_HTTPS_PROXY=http://user:pass@host:port` or `socks5://host:port`.
85
+ const proxy = process.env.JOB_PRO_HTTPS_PROXY?.trim();
86
+ const proxyArg = proxy ? [`--proxy-server=${proxy}`] : [];
87
+ try {
88
+ const browser = await pp.mod.launch({
89
+ executablePath: chrome,
90
+ headless: true,
91
+ args: [
92
+ "--no-sandbox",
93
+ "--disable-blink-features=AutomationControlled",
94
+ "--disable-features=IsolateOrigins,site-per-process",
95
+ ...proxyArg,
96
+ ],
97
+ });
98
+ return browser;
99
+ }
100
+ catch (err) {
101
+ return {
102
+ reason: "launch-failed",
103
+ message: `Chrome failed to launch: ${err instanceof Error ? err.message : String(err)}`,
104
+ };
105
+ }
106
+ }
107
+ /** Get a process-singleton headless browser. Subsequent calls reuse it. */
108
+ export async function getBrowser() {
109
+ if (_browser)
110
+ return { ok: true, browser: _browser };
111
+ if (_browserError)
112
+ return { ok: false, error: _browserError };
113
+ if (!_launching) {
114
+ _launching = launchOnce();
115
+ }
116
+ const result = await _launching;
117
+ _launching = null;
118
+ if ("reason" in result) {
119
+ _browserError = result;
120
+ return { ok: false, error: result };
121
+ }
122
+ _browser = result;
123
+ return { ok: true, browser: result };
124
+ }
125
+ /** Close the singleton browser (call before process exit). */
126
+ export async function closeBrowser() {
127
+ if (_browser) {
128
+ try {
129
+ await _browser.close();
130
+ }
131
+ catch {
132
+ /* ignore */
133
+ }
134
+ _browser = null;
135
+ }
136
+ }
137
+ // On Node exit, best-effort close the browser to avoid zombie processes.
138
+ let _exitHookInstalled = false;
139
+ function ensureExitHook() {
140
+ if (_exitHookInstalled)
141
+ return;
142
+ _exitHookInstalled = true;
143
+ const cleanup = () => {
144
+ if (_browser) {
145
+ try {
146
+ // synchronous best-effort kill; puppeteer launches Chrome as a child
147
+ // process tracked by the Browser object, so close() handles SIGTERM.
148
+ void _browser.close().catch(() => undefined);
149
+ }
150
+ catch {
151
+ /* ignore */
152
+ }
153
+ }
154
+ };
155
+ process.on("exit", cleanup);
156
+ process.on("SIGINT", () => {
157
+ cleanup();
158
+ process.exit(130);
159
+ });
160
+ process.on("SIGTERM", () => {
161
+ cleanup();
162
+ process.exit(143);
163
+ });
164
+ }
165
+ /** Open a page, run fn against it, and close the page. The singleton browser stays open. */
166
+ export async function withPage(fn) {
167
+ ensureExitHook();
168
+ const b = await getBrowser();
169
+ if (!b.ok)
170
+ return b;
171
+ let page = null;
172
+ try {
173
+ page = await b.browser.newPage();
174
+ await page.setUserAgent(USER_AGENT);
175
+ const value = await fn(page);
176
+ return { ok: true, value };
177
+ }
178
+ catch (err) {
179
+ return {
180
+ ok: false,
181
+ error: {
182
+ reason: "launch-failed",
183
+ message: `page operation failed: ${err instanceof Error ? err.message : String(err)}`,
184
+ },
185
+ };
186
+ }
187
+ finally {
188
+ if (page) {
189
+ try {
190
+ await page.close();
191
+ }
192
+ catch {
193
+ /* ignore */
194
+ }
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Inject a set of captured cookies (from extension/) into the singleton
200
+ * browser, so the next withPage call navigates as the logged-in user.
201
+ * Cookies are scoped to the host they were captured from.
202
+ */
203
+ export async function injectCookies(cookies, defaultHost) {
204
+ const b = await getBrowser();
205
+ if (!b.ok)
206
+ return b;
207
+ const toInject = [];
208
+ for (const c of cookies) {
209
+ if (!c.name || !c.value)
210
+ continue;
211
+ const domain = c.domain ?? defaultHost;
212
+ toInject.push({
213
+ name: c.name,
214
+ value: c.value,
215
+ domain,
216
+ path: c.path ?? "/",
217
+ secure: c.secure ?? true,
218
+ httpOnly: c.httpOnly ?? false,
219
+ sameSite: (c.sameSite ?? "Lax"),
220
+ expires: typeof c.expiresAt === "number" ? c.expiresAt : undefined,
221
+ });
222
+ }
223
+ try {
224
+ if (toInject.length > 0)
225
+ await b.browser.setCookie(...toInject);
226
+ return { ok: true };
227
+ }
228
+ catch (err) {
229
+ return {
230
+ ok: false,
231
+ error: {
232
+ reason: "launch-failed",
233
+ message: `cookie injection failed: ${err instanceof Error ? err.message : String(err)}`,
234
+ },
235
+ };
236
+ }
237
+ }
package/dist/cicc.js ADDED
@@ -0,0 +1,56 @@
1
+ // 中金 / CICC careers adapter — Liepin aggregator fallback.
2
+ //
3
+ // CICC's official careers portal (careers.cicc.com / cicc.com.cn) is
4
+ // Cloudflare-gated with HTTP 521 for non-CN IPs, returns 404 for crawlers,
5
+ // and has no third-party ATS (Moka / Beisen / Greenhouse) tenant. We
6
+ // surface real currently-open CICC positions by querying Liepin
7
+ // (api-c.liepin.com) filtered by compName="中金公司". See
8
+ // `cli/src/liepin.ts` for the shared factory.
9
+ //
10
+ // Source: api-c.liepin.com (`source` field on responses) — clearly NOT
11
+ // the same as CICC's own portal. Callers can filter on this attribution
12
+ // if they only want first-party feeds.
13
+ import { createAdapter } from "./liepin.js";
14
+ const adapter = createAdapter({
15
+ companyName: "中金公司",
16
+ label: "CICC / 中金",
17
+ });
18
+ export const searchPositions = adapter.searchPositions;
19
+ export const fetchAllPositions = adapter.fetchAllPositions;
20
+ export const fetchPositionDetail = adapter.fetchPositionDetail;
21
+ export const fetchDictionaries = adapter.fetchDictionaries;
22
+ export const listNotices = adapter.listNotices;
23
+ export const getNotice = adapter.getNotice;
24
+ export const findNoticesByQuestion = adapter.findNoticesByQuestion;
25
+ export const matchResume = adapter.matchResume;
26
+ export const checkResume = adapter.checkResume;
27
+ import { buildBespokeApplySchema as _buildBespokeApplySchema_cicc } from "./apply.js";
28
+ export async function fetchApplicationSchema(postId) {
29
+ const id = (postId ?? "").trim();
30
+ if (!id)
31
+ return { ok: false, source: "cicc.com (via api-c.liepin.com)", message: "post_id is required" };
32
+ let title = "";
33
+ let applyUrl = "https://cicc.com";
34
+ try {
35
+ const detail = (await fetchPositionDetail(id));
36
+ if (detail?.ok === false) {
37
+ return { ok: false, source: "cicc.com (via api-c.liepin.com)", message: detail.message ?? "post not found" };
38
+ }
39
+ title = detail?.title ?? "";
40
+ if (detail?.apply_url)
41
+ applyUrl = detail.apply_url;
42
+ }
43
+ catch { }
44
+ return {
45
+ ok: true,
46
+ schema: _buildBespokeApplySchema_cicc({
47
+ source: "cicc.com (via api-c.liepin.com)",
48
+ postId: id,
49
+ jobTitle: title,
50
+ applyUrl,
51
+ submitEndpoint: undefined,
52
+ submitKind: "external",
53
+ submitNotes: "CICC (Liepin-backed) — submission is recruiter-IM-mediated through Liepin. Open the apply_url to start the chat.",
54
+ }),
55
+ };
56
+ }
@@ -0,0 +1,60 @@
1
+ // Canonical static map of which adapters have an end-to-end-verified apply
2
+ // endpoint. Mirrors `endpoint_verified: true` declarations across the per-
3
+ // adapter schemas so the CLI can answer "is X apply-ready right now?"
4
+ // without firing 50 schema fetches.
5
+ //
6
+ // Update flow (when promoting a 🔑 to ✅):
7
+ // 1. The adapter's fetchApplicationSchema → `endpointVerified: true` on
8
+ // buildBespokeApplySchema, or `endpoint_verified: true` on the literal
9
+ // schema for family-factory adapters (feishu/moka/wecruit/beisen-italent).
10
+ // 2. Add the adapter key here.
11
+ // 3. Add a row in `pnpm test:debug-submit` if it's wire-format-testable.
12
+ //
13
+ // Audited at: 1.0.78 — count must equal 45 (all non-external).
14
+ export const ENDPOINT_VERIFIED = new Set([
15
+ // multipart-anon (end-to-end smoked via httpbin)
16
+ "xpeng", "weride", "hoyoverse",
17
+ // multipart-session (anon-probe-verified)
18
+ "alibaba", "pdd", "meituan", "mihoyo", "liauto",
19
+ // moka-aes (anon-probe-verified — AES envelope)
20
+ "moonshot", "megvii", "deepseek", "galaxyuniversal", "stepfun", "cambricon", "geely",
21
+ // beisen-italent (anon-probe-verified — IIS 500 template)
22
+ "iflytek", "vivo",
23
+ // multipart-session probe-verified via re-routing (1.0.50)
24
+ "sf",
25
+ // multipart-session probe-verified via 405 (route exists, method/body wrong)
26
+ "netease", "didi", "pingan",
27
+ // probe-verified via re-routed sub-tree + JWT gateway response (1.0.52)
28
+ "byd",
29
+ // probe-verified via re-routed sub-tree (1.0.53)
30
+ "bilibili",
31
+ // probe-verified via host-root path (1.0.54)
32
+ "xiaohongshu",
33
+ // probe-verified via host-root + auth-middleware (1.0.55)
34
+ "baidu",
35
+ // probe-verified via JS-bundle string extraction (1.0.57)
36
+ "tencent",
37
+ // verified via JS-bundle path extraction + cross-domain check (1.0.58)
38
+ "jd",
39
+ // probe-verified via Spring 500 + JS-bundle sub-tree discovery (1.0.59)
40
+ "oppo",
41
+ // probe-verified via JS-bundle extraction (1.0.60)
42
+ "trip",
43
+ // Feishu family: /api/v1/user/applications discovered via SPA chunk 4026
44
+ // (1.0.62). Promotes all 8 Feishu adapters since they share backend.
45
+ "xiaomi", "nio", "minimax", "zhipu", "iqiyi", "agibot", "zerooneai", "baichuan",
46
+ // bytedance: atsx-throne tenant, same /api/v1/user/applications (1.0.63)
47
+ "bytedance",
48
+ // Beisen Wecruit family: anon probe with X-Requested-With (1.0.63)
49
+ "sensetime", "horizonrobotics",
50
+ // kuaishou: /recruit/campus/e/api/v1/ sub-tree discovered (1.0.64)
51
+ "kuaishou",
52
+ // weibo: proxies to Moka (verified earlier) (1.0.65)
53
+ "weibo",
54
+ // huawei: /reccampportal/services/portal/portaluser/ Jalor framework (1.0.66)
55
+ "huawei",
56
+ // lilith: atsx-throne tenant, /api/v1/user/applications 405 (1.0.67)
57
+ "lilith",
58
+ // antgroup: talent.antgroup.com second umi bundle revealed (1.0.68)
59
+ "antgroup",
60
+ ]);
@@ -0,0 +1,25 @@
1
+ // DeepSeek (深度求索) / High-Flyer (幻方量化) careers — Moka SSR + AES-128-CBC.
2
+ //
3
+ // Portal: https://app.mokahr.com/social-recruitment/high-flyer/140576
4
+ // (High-Flyer is the parent quant fund; DeepSeek's careers share the
5
+ // same Moka tenant.) Probed 2026-05; ~37 social-hire positions.
6
+ // See cli/src/moka.ts for the shared factory.
7
+ import { createAdapter } from "./moka.js";
8
+ const adapter = createAdapter({
9
+ orgSlug: "high-flyer",
10
+ label: "DeepSeek / High-Flyer",
11
+ channels: [
12
+ { siteId: 140576, kind: "social-recruitment", recruitType: "social" },
13
+ ],
14
+ defaultRecruitType: "social",
15
+ });
16
+ export const searchPositions = adapter.searchPositions;
17
+ export const fetchAllPositions = adapter.fetchAllPositions;
18
+ export const fetchPositionDetail = adapter.fetchPositionDetail;
19
+ export const fetchDictionaries = adapter.fetchDictionaries;
20
+ export const listNotices = adapter.listNotices;
21
+ export const getNotice = adapter.getNotice;
22
+ export const findNoticesByQuestion = adapter.findNoticesByQuestion;
23
+ export const matchResume = adapter.matchResume;
24
+ export const checkResume = adapter.checkResume;
25
+ export const fetchApplicationSchema = adapter.fetchApplicationSchema;