@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/memory.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Tiny JSON-backed key/value + event log, stored at $JOBPRO_HOME/tencent-memory.json
|
|
2
|
+
// (defaults to ~/.jobpro/). Intentionally schema-loose so future companies
|
|
3
|
+
// can share the same file with their own namespaced keys.
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
function memoryPath() {
|
|
8
|
+
const base = process.env.JOBPRO_HOME ?? join(homedir(), ".jobpro");
|
|
9
|
+
mkdirSync(base, { recursive: true });
|
|
10
|
+
return join(base, "tencent-memory.json");
|
|
11
|
+
}
|
|
12
|
+
function load() {
|
|
13
|
+
const path = memoryPath();
|
|
14
|
+
if (!existsSync(path))
|
|
15
|
+
return { fields: {}, events: [] };
|
|
16
|
+
try {
|
|
17
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
18
|
+
return {
|
|
19
|
+
fields: raw.fields ?? {},
|
|
20
|
+
events: raw.events ?? [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { fields: {}, events: [] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function save(data) {
|
|
28
|
+
writeFileSync(memoryPath(), JSON.stringify(data, null, 2), "utf8");
|
|
29
|
+
}
|
|
30
|
+
export function memoryList() {
|
|
31
|
+
return { ok: true, path: memoryPath(), ...load() };
|
|
32
|
+
}
|
|
33
|
+
export function memoryGet(key) {
|
|
34
|
+
return { ok: true, key, value: load().fields[key] };
|
|
35
|
+
}
|
|
36
|
+
export function memorySet(pairs) {
|
|
37
|
+
if (!pairs.length) {
|
|
38
|
+
return { ok: false, message: "no key=value pairs provided" };
|
|
39
|
+
}
|
|
40
|
+
const data = load();
|
|
41
|
+
const applied = {};
|
|
42
|
+
for (const pair of pairs) {
|
|
43
|
+
const idx = pair.indexOf("=");
|
|
44
|
+
if (idx <= 0) {
|
|
45
|
+
return { ok: false, message: `expected key=value, got: ${JSON.stringify(pair)}` };
|
|
46
|
+
}
|
|
47
|
+
const key = pair.slice(0, idx).trim();
|
|
48
|
+
const value = pair.slice(idx + 1);
|
|
49
|
+
if (!key) {
|
|
50
|
+
return { ok: false, message: `empty key in ${JSON.stringify(pair)}` };
|
|
51
|
+
}
|
|
52
|
+
data.fields[key] = value;
|
|
53
|
+
applied[key] = value;
|
|
54
|
+
}
|
|
55
|
+
save(data);
|
|
56
|
+
return { ok: true, applied, path: memoryPath() };
|
|
57
|
+
}
|
|
58
|
+
export function memoryEvent(kind, payload = "") {
|
|
59
|
+
if (!kind)
|
|
60
|
+
return { ok: false, message: "event kind is required" };
|
|
61
|
+
const data = load();
|
|
62
|
+
const entry = {
|
|
63
|
+
ts: new Date().toISOString().slice(0, 19),
|
|
64
|
+
kind,
|
|
65
|
+
payload,
|
|
66
|
+
};
|
|
67
|
+
data.events.push(entry);
|
|
68
|
+
save(data);
|
|
69
|
+
return { ok: true, event: entry, total_events: data.events.length };
|
|
70
|
+
}
|
|
71
|
+
export function memoryClear() {
|
|
72
|
+
const path = memoryPath();
|
|
73
|
+
if (existsSync(path))
|
|
74
|
+
rmSync(path);
|
|
75
|
+
return { ok: true, path, message: "memory cleared" };
|
|
76
|
+
}
|
package/dist/mihoyo.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Thin client for 米哈游 / miHoYo recruiting portal.
|
|
2
|
+
//
|
|
3
|
+
// Portal: https://jobs.mihoyo.com/ (the old campus.mihoyo.com permanently
|
|
4
|
+
// redirects here)
|
|
5
|
+
// API host: https://ats.openout.mihoyo.com/ats-portal
|
|
6
|
+
//
|
|
7
|
+
// ============================================================
|
|
8
|
+
// Discovery (2026-05):
|
|
9
|
+
//
|
|
10
|
+
// campus.mihoyo.com → permanently redirects to jobs.mihoyo.com
|
|
11
|
+
// jobs.mihoyo.com → React SPA shell
|
|
12
|
+
// ats.openout.mihoyo.com → real ATS backend (in the bundle: baseURL)
|
|
13
|
+
//
|
|
14
|
+
// The bundle whitelist contains /v1/job/category/list, /v1/job/get/id_list,
|
|
15
|
+
// /v1/job/project_count/list — but the actual search endpoint that returns
|
|
16
|
+
// summarized job rows (the one the SPA hits to render the list page) is
|
|
17
|
+
// /v1/job/list (probed; unauth-OK with channelDetailIds + hireType + pageNo).
|
|
18
|
+
// /v1/job/info gives full per-position detail.
|
|
19
|
+
//
|
|
20
|
+
// "channel" semantics (decoded from the bundle's enums):
|
|
21
|
+
// R.CAMPUS = 1, R.JOBS = 1 (same value), R.RECOMMEND = 2
|
|
22
|
+
// hireType enum: JOBS = 0 (social), CAMPUS = 1
|
|
23
|
+
// Default surface = social: channelDetailIds=[1], hireType=0.
|
|
24
|
+
//
|
|
25
|
+
// ============================================================
|
|
26
|
+
// Response shape (probed 2026-05):
|
|
27
|
+
// data.list[]:
|
|
28
|
+
// id, title, competencyType, jobNature, projectName,
|
|
29
|
+
// addressDetailList[].addressDetail, channelDetailIds
|
|
30
|
+
// data.total — canonical total count
|
|
31
|
+
//
|
|
32
|
+
// PositionSummary field mapping:
|
|
33
|
+
// post_id ← String(job.id)
|
|
34
|
+
// title ← job.title
|
|
35
|
+
// project ← job.competencyType (job category)
|
|
36
|
+
// recruit_label ← job.jobNature ("全职" / "实习")
|
|
37
|
+
// bgs ← job.projectName ("社会招聘" / "校园招聘")
|
|
38
|
+
// work_cities ← addressDetailList[].addressDetail joined " / "
|
|
39
|
+
// apply_url ← https://jobs.mihoyo.com/#/position/${id}
|
|
40
|
+
import { extractResumeSignals, scoreOverlap, checkResume } from "./tencent.js";
|
|
41
|
+
export { checkResume, extractResumeSignals, scoreOverlap };
|
|
42
|
+
const SOURCE = "jobs.mihoyo.com";
|
|
43
|
+
const API_ROOT = "https://ats.openout.mihoyo.com/ats-portal";
|
|
44
|
+
const PORTAL_URL = "https://jobs.mihoyo.com";
|
|
45
|
+
const APPLY_URL_PREFIX = `${PORTAL_URL}/#/position`;
|
|
46
|
+
// Default channel: social ("社招"). Bundle constant R.JOBS = 1.
|
|
47
|
+
const CHANNEL_DETAIL_IDS = [1];
|
|
48
|
+
const HIRE_TYPE_SOCIAL = 0;
|
|
49
|
+
const HEADERS = {
|
|
50
|
+
"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",
|
|
51
|
+
Accept: "application/json, text/plain, */*",
|
|
52
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
Origin: PORTAL_URL,
|
|
55
|
+
Referer: `${PORTAL_URL}/`,
|
|
56
|
+
};
|
|
57
|
+
async function postJson(path, body) {
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await fetch(`${API_ROOT}${path}`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: HEADERS,
|
|
63
|
+
body: JSON.stringify(body),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return { ok: false, message: `network error: ${err instanceof Error ? err.message : err}` };
|
|
68
|
+
}
|
|
69
|
+
if (!response.ok)
|
|
70
|
+
return { ok: false, message: `HTTP ${response.status}` };
|
|
71
|
+
let payload;
|
|
72
|
+
try {
|
|
73
|
+
payload = (await response.json());
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, message: `bad JSON: ${err instanceof Error ? err.message : err}` };
|
|
77
|
+
}
|
|
78
|
+
if (payload.code !== 0 || !payload.data) {
|
|
79
|
+
return { ok: false, message: payload.message || "upstream error" };
|
|
80
|
+
}
|
|
81
|
+
return { ok: true, data: payload.data, message: "ok" };
|
|
82
|
+
}
|
|
83
|
+
function summarize(row) {
|
|
84
|
+
const id = String(row.id ?? "");
|
|
85
|
+
const cities = (row.addressDetailList ?? [])
|
|
86
|
+
.map((a) => a.addressDetail ?? "")
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
return {
|
|
89
|
+
post_id: id,
|
|
90
|
+
title: row.title ?? "",
|
|
91
|
+
project: row.competencyType ?? "",
|
|
92
|
+
recruit_label: row.jobNature ?? "",
|
|
93
|
+
bgs: row.projectName ?? "",
|
|
94
|
+
work_cities: cities.join(" / "),
|
|
95
|
+
apply_url: id ? `${APPLY_URL_PREFIX}/${encodeURIComponent(id)}` : PORTAL_URL,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ---------- searchPositions ----------
|
|
99
|
+
export async function searchPositions(opts = {}) {
|
|
100
|
+
const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 20));
|
|
101
|
+
const page = Math.max(1, opts.page ?? 1);
|
|
102
|
+
const keyword = (opts.keyword ?? "").trim().slice(0, 60);
|
|
103
|
+
const body = {
|
|
104
|
+
channelDetailIds: opts.channelDetailIds ?? CHANNEL_DETAIL_IDS,
|
|
105
|
+
hireType: opts.hireType ?? HIRE_TYPE_SOCIAL,
|
|
106
|
+
pageSize,
|
|
107
|
+
pageNo: page,
|
|
108
|
+
};
|
|
109
|
+
if (keyword)
|
|
110
|
+
body.jobName = keyword;
|
|
111
|
+
const response = await postJson("/v1/job/list", body);
|
|
112
|
+
if (!response.ok || !response.data) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
message: response.message,
|
|
116
|
+
source: SOURCE,
|
|
117
|
+
query: body,
|
|
118
|
+
positions: [],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const rows = response.data.list ?? [];
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
source: SOURCE,
|
|
125
|
+
query: body,
|
|
126
|
+
page,
|
|
127
|
+
page_size: pageSize,
|
|
128
|
+
total: response.data.total ?? rows.length,
|
|
129
|
+
positions: rows.map(summarize),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ---------- fetchAllPositions ----------
|
|
133
|
+
export async function fetchAllPositions(opts = {}) {
|
|
134
|
+
const pageSize = Math.max(1, Math.min(100, opts.pageSize ?? 100));
|
|
135
|
+
const maxPages = Math.max(1, opts.maxPages ?? 10);
|
|
136
|
+
const bucket = [];
|
|
137
|
+
let total;
|
|
138
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
139
|
+
const result = await searchPositions({ ...opts, page, pageSize });
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
message: result.message,
|
|
144
|
+
source: SOURCE,
|
|
145
|
+
fetched: bucket.length,
|
|
146
|
+
positions: bucket,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (total === undefined)
|
|
150
|
+
total = result.total;
|
|
151
|
+
if (!result.positions.length)
|
|
152
|
+
break;
|
|
153
|
+
bucket.push(...result.positions);
|
|
154
|
+
if (total !== undefined && bucket.length >= total)
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
source: SOURCE,
|
|
160
|
+
total: total ?? bucket.length,
|
|
161
|
+
fetched: bucket.length,
|
|
162
|
+
positions: bucket,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ---------- fetchPositionDetail ----------
|
|
166
|
+
export async function fetchPositionDetail(postId) {
|
|
167
|
+
const id = (postId ?? "").trim();
|
|
168
|
+
if (!id)
|
|
169
|
+
return { ok: false, source: SOURCE, message: "post_id is required" };
|
|
170
|
+
// /v1/job/info requires channelDetailIds (the channel mhy splits social /
|
|
171
|
+
// campus / intern positions on); without it the API returns "职位渠道不可
|
|
172
|
+
// 以为空" even for a valid post id.
|
|
173
|
+
const response = await postJson("/v1/job/info", { id, channelDetailIds: CHANNEL_DETAIL_IDS });
|
|
174
|
+
if (!response.ok || !response.data) {
|
|
175
|
+
return { ok: false, source: SOURCE, message: response.message, post_id: id };
|
|
176
|
+
}
|
|
177
|
+
const d = response.data;
|
|
178
|
+
const summary = summarize(d);
|
|
179
|
+
return {
|
|
180
|
+
ok: true,
|
|
181
|
+
source: SOURCE,
|
|
182
|
+
post_id: summary.post_id,
|
|
183
|
+
title: d.title ?? "",
|
|
184
|
+
direction: d.objectName ?? "",
|
|
185
|
+
description: d.description ?? "",
|
|
186
|
+
requirements: d.jobRequire ?? "",
|
|
187
|
+
addition: d.addition ?? "",
|
|
188
|
+
work_cities: d.addressDetailList ?? [],
|
|
189
|
+
project: d.competencyType ?? "",
|
|
190
|
+
recruit_label: d.jobNature ?? "",
|
|
191
|
+
hire_type_name: d.hireTypeName ?? "",
|
|
192
|
+
apply_url: summary.apply_url,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// ---------- fetchDictionaries ----------
|
|
196
|
+
let _filterCache = null;
|
|
197
|
+
export async function fetchDictionaries() {
|
|
198
|
+
if (_filterCache !== null)
|
|
199
|
+
return _filterCache;
|
|
200
|
+
const social = await postJson("/v1/job/category/list", {
|
|
201
|
+
channelDetailIds: CHANNEL_DETAIL_IDS,
|
|
202
|
+
hireType: HIRE_TYPE_SOCIAL,
|
|
203
|
+
});
|
|
204
|
+
const campus = await postJson("/v1/job/category/list", { channelDetailIds: CHANNEL_DETAIL_IDS, hireType: 1 });
|
|
205
|
+
if (!social.ok && !campus.ok) {
|
|
206
|
+
const result = {
|
|
207
|
+
ok: false,
|
|
208
|
+
source: SOURCE,
|
|
209
|
+
message: social.message || campus.message,
|
|
210
|
+
};
|
|
211
|
+
_filterCache = result;
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
const mapList = (list) => list.map((c) => ({
|
|
215
|
+
competencyType: c.competencyType ?? "",
|
|
216
|
+
competencyTypeName: c.competencyTypeName ?? "",
|
|
217
|
+
competencyTypeEnName: c.competencyTypeEnName ?? "",
|
|
218
|
+
count: c.count ?? 0,
|
|
219
|
+
}));
|
|
220
|
+
const result = {
|
|
221
|
+
ok: true,
|
|
222
|
+
source: SOURCE,
|
|
223
|
+
categories_social: social.ok && social.data ? mapList(social.data) : [],
|
|
224
|
+
categories_campus: campus.ok && campus.data ? mapList(campus.data) : [],
|
|
225
|
+
};
|
|
226
|
+
_filterCache = result;
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
// ---------- stub notices ----------
|
|
230
|
+
const NOTICES_STUB = {
|
|
231
|
+
ok: false,
|
|
232
|
+
source: SOURCE,
|
|
233
|
+
message: "miHoYo: no public notices endpoint",
|
|
234
|
+
};
|
|
235
|
+
export async function listNotices() {
|
|
236
|
+
return { ...NOTICES_STUB, notices: [] };
|
|
237
|
+
}
|
|
238
|
+
export async function getNotice(noticeId) {
|
|
239
|
+
return { ...NOTICES_STUB, notice_id: noticeId };
|
|
240
|
+
}
|
|
241
|
+
export async function findNoticesByQuestion(question, _opts = {}) {
|
|
242
|
+
return { ...NOTICES_STUB, question, matches: [] };
|
|
243
|
+
}
|
|
244
|
+
// ---------- matchResume ----------
|
|
245
|
+
export async function matchResume(text, opts = {}) {
|
|
246
|
+
const topN = Math.max(1, opts.topN ?? 5);
|
|
247
|
+
const candidates = Math.max(topN, opts.candidates ?? 100);
|
|
248
|
+
const { terms, cities } = extractResumeSignals(text ?? "");
|
|
249
|
+
if (!terms.length) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
source: SOURCE,
|
|
253
|
+
message: "could not extract any technical signals from the text",
|
|
254
|
+
preview: (text ?? "").slice(0, 120),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const keyword = terms[0];
|
|
258
|
+
const list = await searchPositions({ keyword, page: 1, pageSize: Math.min(100, candidates) });
|
|
259
|
+
if (!list.ok) {
|
|
260
|
+
return { ok: false, source: SOURCE, message: list.message, positions: [] };
|
|
261
|
+
}
|
|
262
|
+
const scored = (list.positions ?? [])
|
|
263
|
+
.map((p) => ({
|
|
264
|
+
p,
|
|
265
|
+
score: scoreOverlap(`${p.title} ${p.project} ${p.bgs}`, terms, cities).score,
|
|
266
|
+
}))
|
|
267
|
+
.sort((a, b) => b.score - a.score)
|
|
268
|
+
.slice(0, topN)
|
|
269
|
+
.map((x) => x.p);
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
source: SOURCE,
|
|
273
|
+
extracted_terms: terms,
|
|
274
|
+
city_preferences: cities,
|
|
275
|
+
matches: scored,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
import { buildBespokeApplySchema as _buildBespokeApplySchema_mihoyo } from "./apply.js";
|
|
279
|
+
export async function fetchApplicationSchema(postId) {
|
|
280
|
+
const id = (postId ?? "").trim();
|
|
281
|
+
if (!id)
|
|
282
|
+
return { ok: false, source: "ats.openout.mihoyo.com", message: "post_id is required" };
|
|
283
|
+
let title = "";
|
|
284
|
+
let applyUrl = "https://ats.openout.mihoyo.com";
|
|
285
|
+
try {
|
|
286
|
+
const detail = (await fetchPositionDetail(id));
|
|
287
|
+
if (detail?.ok === false) {
|
|
288
|
+
return { ok: false, source: "ats.openout.mihoyo.com", message: detail.message ?? "post not found" };
|
|
289
|
+
}
|
|
290
|
+
title = detail?.title ?? "";
|
|
291
|
+
if (detail?.apply_url)
|
|
292
|
+
applyUrl = detail.apply_url;
|
|
293
|
+
}
|
|
294
|
+
catch { }
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
schema: _buildBespokeApplySchema_mihoyo({
|
|
298
|
+
source: "ats.openout.mihoyo.com",
|
|
299
|
+
postId: id,
|
|
300
|
+
jobTitle: title,
|
|
301
|
+
applyUrl,
|
|
302
|
+
submitEndpoint: "https://ats.openout.mihoyo.com/ats-portal/v1/application/create",
|
|
303
|
+
submitKind: "multipart-session",
|
|
304
|
+
endpointVerified: true,
|
|
305
|
+
submitNotes: "miHoYo — POST /ats-portal/v1/application/create with session cookie. Endpoint anon-probed → {code: -3, message: \"用户未登录或登录失效\"} (real auth gate). Body shape still needs validation.",
|
|
306
|
+
}),
|
|
307
|
+
};
|
|
308
|
+
}
|
package/dist/minimax.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Thin adapter for MiniMax / MiniMax智能 campus recruiting via Feishu Recruiting (ATSX).
|
|
2
|
+
//
|
|
3
|
+
// MiniMax uses the Feishu multi-tenant portal. Their career page is:
|
|
4
|
+
// https://vrfi1sk8a0.jobs.feishu.cn/379481/
|
|
5
|
+
// (linked from https://www.minimax.io/careers)
|
|
6
|
+
//
|
|
7
|
+
// API (probed 2026-05):
|
|
8
|
+
// POST https://vrfi1sk8a0.jobs.feishu.cn/api/v1/search/job/posts
|
|
9
|
+
// Total: ~83 posts (intern + full-time combined; no separate channel scoping)
|
|
10
|
+
// GET https://vrfi1sk8a0.jobs.feishu.cn/api/v1/config/job/filters/379481
|
|
11
|
+
//
|
|
12
|
+
// ---- Critical discovery: multi-tenant portal-channel ----
|
|
13
|
+
// Unlike company-dedicated subdomains (e.g. nio.jobs.feishu.cn uses "campus"),
|
|
14
|
+
// multi-tenant portals use the COMPANY PATH as the portal-channel value.
|
|
15
|
+
// For MiniMax (path "379481"):
|
|
16
|
+
// portal-channel: "379481" ← NOT "campus"
|
|
17
|
+
// website-path: "379481" ← NOT "campus"
|
|
18
|
+
// Using "campus" returns {"code":-9000003,"message":"site not exist"}.
|
|
19
|
+
//
|
|
20
|
+
// Field notes:
|
|
21
|
+
// - job_function is null; project ← job_category.name
|
|
22
|
+
// - city_info is null; work_cities ← city_list (may have multiple cities)
|
|
23
|
+
// - Recruit types include both 实习 and 正式 in the same pool (no channel split)
|
|
24
|
+
//
|
|
25
|
+
// apply_url pattern: https://vrfi1sk8a0.jobs.feishu.cn/379481/position/<id>/detail
|
|
26
|
+
import { createAdapter } from "./feishu.js";
|
|
27
|
+
export const { searchPositions, fetchAllPositions, fetchPositionDetail, fetchDictionaries, listNotices, getNotice, findNoticesByQuestion, matchResume, checkResume, fetchApplicationSchema, } = createAdapter({
|
|
28
|
+
host: "vrfi1sk8a0.jobs.feishu.cn",
|
|
29
|
+
channel: "379481",
|
|
30
|
+
label: "MiniMax / MiniMax智能",
|
|
31
|
+
applyUrlPrefix: "https://vrfi1sk8a0.jobs.feishu.cn/379481/position",
|
|
32
|
+
});
|