job-pro 1.0.5 → 1.0.7
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/apply.js +132 -163
- package/dist/index.js +86 -6
- package/package.json +1 -1
package/dist/apply.js
CHANGED
|
@@ -576,33 +576,20 @@ export async function executeFeishu3Step(staged, session, target) {
|
|
|
576
576
|
const sessionHeaders = sessionHeaderBag(session, host);
|
|
577
577
|
// STEP 1 — upload tokens
|
|
578
578
|
const step1Url = debug ? target.url : `${apiRoot}/attachment/upload/tokens`;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
catch (err) {
|
|
592
|
-
steps.push({ step: "upload-tokens", url: step1Url, status: 0, ok: false, message: String(err) });
|
|
593
|
-
return { ok: false, posted_to: step1Url, message: "step 1 network error", steps };
|
|
594
|
-
}
|
|
595
|
-
const step1Body = await step1Resp.text();
|
|
596
|
-
steps.push({
|
|
597
|
-
step: "upload-tokens",
|
|
598
|
-
url: step1Url,
|
|
599
|
-
status: step1Resp.status,
|
|
600
|
-
ok: step1Resp.ok,
|
|
601
|
-
message: step1Body.slice(0, 200),
|
|
602
|
-
});
|
|
603
|
-
if (!step1Resp.ok) {
|
|
604
|
-
return { ok: false, posted_to: step1Url, status: step1Resp.status, message: "step 1 failed (upload tokens)", steps, response_preview: step1Body.slice(0, 4000) };
|
|
579
|
+
const s1 = await doStep("upload-tokens", step1Url, {
|
|
580
|
+
method: "POST",
|
|
581
|
+
headers: {
|
|
582
|
+
...sessionHeaders,
|
|
583
|
+
"Content-Type": "application/json",
|
|
584
|
+
Accept: "application/json, text/plain, */*",
|
|
585
|
+
},
|
|
586
|
+
body: JSON.stringify({ filename, file_size: fileSize }),
|
|
587
|
+
}, steps);
|
|
588
|
+
if (!s1.ok) {
|
|
589
|
+
return { ok: false, posted_to: step1Url, status: s1.status, message: `step 1 failed: ${s1.message}`, steps };
|
|
605
590
|
}
|
|
591
|
+
const step1Resp = s1.response;
|
|
592
|
+
const step1Body = s1.text;
|
|
606
593
|
// In debug mode, we don't actually have a presigned URL — short-circuit.
|
|
607
594
|
if (debug) {
|
|
608
595
|
return {
|
|
@@ -640,23 +627,14 @@ export async function executeFeishu3Step(staged, session, target) {
|
|
|
640
627
|
? new FileCtor([new Uint8Array(resumeBytes)], filename, { type: "application/pdf" })
|
|
641
628
|
: new Blob([new Uint8Array(resumeBytes)], { type: "application/pdf" });
|
|
642
629
|
uploadFd.append("file", filePart, filename);
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
630
|
+
const s2 = await doStep("upload-file", upload_url, { method: "POST", body: uploadFd }, steps);
|
|
631
|
+
if (!s2.ok) {
|
|
632
|
+
return { ok: false, posted_to: upload_url, status: s2.status, message: `step 2 failed: ${s2.message}`, steps };
|
|
646
633
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
steps.push({
|
|
652
|
-
step: "upload-file",
|
|
653
|
-
url: upload_url,
|
|
654
|
-
status: step2Resp.status,
|
|
655
|
-
ok: step2Resp.ok,
|
|
656
|
-
message: `HTTP ${step2Resp.status}`,
|
|
657
|
-
});
|
|
658
|
-
if (!step2Resp.ok) {
|
|
659
|
-
return { ok: false, posted_to: upload_url, status: step2Resp.status, message: "step 2 failed (upload to CDN)", steps };
|
|
634
|
+
// s2 already pushed to steps via doStep; if upstream returned non-2xx
|
|
635
|
+
// (after retries on 5xx), surface that.
|
|
636
|
+
if (!s2.response.ok) {
|
|
637
|
+
return { ok: false, posted_to: upload_url, status: s2.response.status, message: "step 2 failed (upload to CDN)", steps };
|
|
660
638
|
}
|
|
661
639
|
// STEP 3 — resume/apply
|
|
662
640
|
const applicantInfo = {};
|
|
@@ -671,36 +649,24 @@ export async function executeFeishu3Step(staged, session, target) {
|
|
|
671
649
|
applicant_info: applicantInfo,
|
|
672
650
|
};
|
|
673
651
|
const step3Url = `${apiRoot}/resume/apply`;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
catch (err) {
|
|
687
|
-
steps.push({ step: "resume-apply", url: step3Url, status: 0, ok: false, message: String(err) });
|
|
688
|
-
return { ok: false, posted_to: step3Url, message: "step 3 network error", steps };
|
|
652
|
+
const s3 = await doStep("resume-apply", step3Url, {
|
|
653
|
+
method: "POST",
|
|
654
|
+
headers: {
|
|
655
|
+
...sessionHeaders,
|
|
656
|
+
"Content-Type": "application/json",
|
|
657
|
+
Accept: "application/json, text/plain, */*",
|
|
658
|
+
},
|
|
659
|
+
body: JSON.stringify(step3Body),
|
|
660
|
+
}, steps);
|
|
661
|
+
if (!s3.ok) {
|
|
662
|
+
return { ok: false, posted_to: step3Url, status: s3.status, message: `step 3 failed: ${s3.message}`, steps };
|
|
689
663
|
}
|
|
690
|
-
const step3Text = await step3Resp.text();
|
|
691
|
-
steps.push({
|
|
692
|
-
step: "resume-apply",
|
|
693
|
-
url: step3Url,
|
|
694
|
-
status: step3Resp.status,
|
|
695
|
-
ok: step3Resp.ok,
|
|
696
|
-
message: step3Text.slice(0, 200),
|
|
697
|
-
});
|
|
698
664
|
return {
|
|
699
|
-
ok:
|
|
700
|
-
status:
|
|
665
|
+
ok: s3.response.ok,
|
|
666
|
+
status: s3.response.status,
|
|
701
667
|
posted_to: step3Url,
|
|
702
|
-
response_preview:
|
|
703
|
-
message:
|
|
668
|
+
response_preview: s3.text,
|
|
669
|
+
message: s3.response.ok ? "Feishu 3-step submission accepted" : `step 3 rejected: HTTP ${s3.response.status}`,
|
|
704
670
|
steps,
|
|
705
671
|
};
|
|
706
672
|
}
|
|
@@ -748,6 +714,42 @@ function retryDelayMs(attempt) {
|
|
|
748
714
|
function sleep(ms) {
|
|
749
715
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
750
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Family-executor convenience wrapper. Combines fetchWithRetry's
|
|
719
|
+
* transient-failure handling with the FeishuStepLog bookkeeping that
|
|
720
|
+
* each executor needs to push into result.steps. Returns the response
|
|
721
|
+
* + decoded text, or the error message; either way appends one entry
|
|
722
|
+
* to `steps[]`.
|
|
723
|
+
*/
|
|
724
|
+
async function doStep(step, url, init, steps) {
|
|
725
|
+
const r = await fetchWithRetry(url, init, step);
|
|
726
|
+
if (!r.ok) {
|
|
727
|
+
steps.push({
|
|
728
|
+
step,
|
|
729
|
+
url,
|
|
730
|
+
status: r.status ?? 0,
|
|
731
|
+
ok: false,
|
|
732
|
+
message: r.message.slice(0, 200),
|
|
733
|
+
});
|
|
734
|
+
return { ok: false, status: r.status, message: r.message };
|
|
735
|
+
}
|
|
736
|
+
const response = r.response;
|
|
737
|
+
let text = "";
|
|
738
|
+
try {
|
|
739
|
+
text = (await response.text()).slice(0, 4000);
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
/* binary or stream */
|
|
743
|
+
}
|
|
744
|
+
steps.push({
|
|
745
|
+
step,
|
|
746
|
+
url,
|
|
747
|
+
status: response.status,
|
|
748
|
+
ok: response.ok,
|
|
749
|
+
message: text.slice(0, 200) || `HTTP ${response.status}`,
|
|
750
|
+
});
|
|
751
|
+
return { ok: true, response, text };
|
|
752
|
+
}
|
|
751
753
|
/** Build the headers bag used by every Feishu/Beisen/Moka step. */
|
|
752
754
|
function sessionHeaderBag(session, targetHost) {
|
|
753
755
|
const out = {
|
|
@@ -838,42 +840,32 @@ export async function executeMokaApply(staged, session, target) {
|
|
|
838
840
|
fd.append("resume", filePart, filename);
|
|
839
841
|
const steps = [];
|
|
840
842
|
const sessionHeaders = sessionHeaderBag(session, host);
|
|
841
|
-
// Pre-flight limit check (optional — skip in debug since we'd redirect)
|
|
843
|
+
// Pre-flight limit check (optional — skip in debug since we'd redirect).
|
|
844
|
+
// Best-effort; we ignore failures here because the upstream submit will
|
|
845
|
+
// surface any blocker more authoritatively.
|
|
842
846
|
if (!debug && session) {
|
|
843
847
|
const lc = `${apiRoot}/api/outer/ats-apply/website/applicant-limit-check`;
|
|
844
|
-
|
|
845
|
-
const resp = await fetch(lc, {
|
|
846
|
-
method: "POST",
|
|
847
|
-
headers: { ...sessionHeaders, "Content-Type": "application/json" },
|
|
848
|
-
body: JSON.stringify({ orgId: slug, jobId: staged.post_id }),
|
|
849
|
-
});
|
|
850
|
-
const txt = (await resp.text()).slice(0, 200);
|
|
851
|
-
steps.push({ step: "limit-check", url: lc, status: resp.status, ok: resp.ok, message: txt });
|
|
852
|
-
}
|
|
853
|
-
catch (err) {
|
|
854
|
-
steps.push({ step: "limit-check", url: lc, status: 0, ok: false, message: String(err) });
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
// Final submit
|
|
858
|
-
let resp;
|
|
859
|
-
try {
|
|
860
|
-
resp = await fetch(targetUrl, {
|
|
848
|
+
await doStep("limit-check", lc, {
|
|
861
849
|
method: "POST",
|
|
862
|
-
headers: sessionHeaders,
|
|
863
|
-
body:
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
catch (err) {
|
|
867
|
-
steps.push({ step: "apply", url: targetUrl, status: 0, ok: false, message: String(err) });
|
|
868
|
-
return { ok: false, posted_to: targetUrl, message: "apply step network error", steps };
|
|
850
|
+
headers: { ...sessionHeaders, "Content-Type": "application/json" },
|
|
851
|
+
body: JSON.stringify({ orgId: slug, jobId: staged.post_id }),
|
|
852
|
+
}, steps);
|
|
869
853
|
}
|
|
870
|
-
|
|
871
|
-
|
|
854
|
+
// Final submit
|
|
855
|
+
const sFinal = await doStep("apply", targetUrl, {
|
|
856
|
+
method: "POST",
|
|
857
|
+
headers: sessionHeaders, // Content-Type: multipart/form-data; boundary set by undici
|
|
858
|
+
body: fd,
|
|
859
|
+
}, steps);
|
|
860
|
+
if (!sFinal.ok) {
|
|
861
|
+
return { ok: false, posted_to: targetUrl, status: sFinal.status, message: `apply failed: ${sFinal.message}`, steps };
|
|
862
|
+
}
|
|
863
|
+
const resp = sFinal.response;
|
|
872
864
|
return {
|
|
873
865
|
ok: resp.ok,
|
|
874
866
|
status: resp.status,
|
|
875
867
|
posted_to: targetUrl,
|
|
876
|
-
response_preview: text,
|
|
868
|
+
response_preview: sFinal.text,
|
|
877
869
|
message: resp.ok ? "Moka apply submitted" : `Moka apply rejected: HTTP ${resp.status}`,
|
|
878
870
|
steps,
|
|
879
871
|
};
|
|
@@ -933,16 +925,12 @@ export async function executeBeisenWecruit(staged, session, target) {
|
|
|
933
925
|
? new FileCtor([new Uint8Array(resumeBytes)], filename, { type: "application/pdf" })
|
|
934
926
|
: new Blob([new Uint8Array(resumeBytes)], { type: "application/pdf" });
|
|
935
927
|
uploadFd.append("file", filePart, filename);
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
}
|
|
940
|
-
catch (err) {
|
|
941
|
-
steps.push({ step: "upload-file", url: step1Url, status: 0, ok: false, message: String(err) });
|
|
942
|
-
return { ok: false, posted_to: step1Url, message: "step 1 network error", steps };
|
|
928
|
+
const s1 = await doStep("upload-file", step1Url, { method: "POST", headers: sessionHeaders, body: uploadFd }, steps);
|
|
929
|
+
if (!s1.ok) {
|
|
930
|
+
return { ok: false, posted_to: step1Url, status: s1.status, message: `step 1 failed: ${s1.message}`, steps };
|
|
943
931
|
}
|
|
944
|
-
const
|
|
945
|
-
|
|
932
|
+
const r1 = s1.response;
|
|
933
|
+
const text1 = s1.text;
|
|
946
934
|
if (debug) {
|
|
947
935
|
return { ok: r1.ok, status: r1.status, posted_to: step1Url, message: "debug: step 1 fired, steps 2+3 skipped", steps, response_preview: text1 };
|
|
948
936
|
}
|
|
@@ -957,36 +945,26 @@ export async function executeBeisenWecruit(staged, session, target) {
|
|
|
957
945
|
catch { /* keep empty */ }
|
|
958
946
|
// STEP 2 — profile info
|
|
959
947
|
const step2Url = `${apiBase}/resume/info/add/${encodeURIComponent(su)}`;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
948
|
+
const s2 = await doStep("profile-add", step2Url, {
|
|
949
|
+
method: "POST",
|
|
950
|
+
headers: { ...sessionHeaders, "Content-Type": "application/json" },
|
|
951
|
+
body: JSON.stringify({ name: applicant.name, email: applicant.email, phone: applicant.phone, attachmentId }),
|
|
952
|
+
}, steps);
|
|
953
|
+
if (!s2.ok) {
|
|
954
|
+
return { ok: false, posted_to: step2Url, status: s2.status, message: `step 2 failed: ${s2.message}`, steps };
|
|
967
955
|
}
|
|
968
|
-
catch (err) {
|
|
969
|
-
steps.push({ step: "profile-add", url: step2Url, status: 0, ok: false, message: String(err) });
|
|
970
|
-
return { ok: false, posted_to: step2Url, message: "step 2 network error", steps };
|
|
971
|
-
}
|
|
972
|
-
const text2 = (await r2.text()).slice(0, 2000);
|
|
973
|
-
steps.push({ step: "profile-add", url: step2Url, status: r2.status, ok: r2.ok, message: text2.slice(0, 200) });
|
|
974
956
|
// STEP 3 — final delivery
|
|
975
957
|
const step3Url = `${apiBase}/delivery/resume/${encodeURIComponent(su)}`;
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
return { ok: false, posted_to: step3Url, message: "step 3 network error", steps };
|
|
987
|
-
}
|
|
988
|
-
const text3 = (await r3.text()).slice(0, 4000);
|
|
989
|
-
steps.push({ step: "deliver", url: step3Url, status: r3.status, ok: r3.ok, message: text3.slice(0, 200) });
|
|
958
|
+
const s3 = await doStep("deliver", step3Url, {
|
|
959
|
+
method: "POST",
|
|
960
|
+
headers: { ...sessionHeaders, "Content-Type": "application/json" },
|
|
961
|
+
body: JSON.stringify({ postId: staged.post_id, attachmentId }),
|
|
962
|
+
}, steps);
|
|
963
|
+
if (!s3.ok) {
|
|
964
|
+
return { ok: false, posted_to: step3Url, status: s3.status, message: `step 3 failed: ${s3.message}`, steps };
|
|
965
|
+
}
|
|
966
|
+
const r3 = s3.response;
|
|
967
|
+
const text3 = s3.text;
|
|
990
968
|
return {
|
|
991
969
|
ok: r3.ok,
|
|
992
970
|
status: r3.status,
|
|
@@ -1050,16 +1028,12 @@ export async function executeBeisenITalent(staged, session, target) {
|
|
|
1050
1028
|
? new FileCtor([new Uint8Array(resumeBytes)], filename, { type: "application/pdf" })
|
|
1051
1029
|
: new Blob([new Uint8Array(resumeBytes)], { type: "application/pdf" });
|
|
1052
1030
|
uploadFd.append("file", filePart, filename);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1031
|
+
const s1 = await doStep("upload", step1Url, { method: "POST", headers: sessionHeaders, body: uploadFd }, steps);
|
|
1032
|
+
if (!s1.ok) {
|
|
1033
|
+
return { ok: false, posted_to: step1Url, status: s1.status, message: `step 1 failed: ${s1.message}`, steps };
|
|
1056
1034
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
return { ok: false, posted_to: step1Url, message: "step 1 network error", steps };
|
|
1060
|
-
}
|
|
1061
|
-
const text1 = (await r1.text()).slice(0, 2000);
|
|
1062
|
-
steps.push({ step: "upload", url: step1Url, status: r1.status, ok: r1.ok, message: text1.slice(0, 200) });
|
|
1035
|
+
const r1 = s1.response;
|
|
1036
|
+
const text1 = s1.text;
|
|
1063
1037
|
if (debug) {
|
|
1064
1038
|
return { ok: r1.ok, status: r1.status, posted_to: step1Url, message: "debug: step 1 fired, step 2 skipped", steps, response_preview: text1 };
|
|
1065
1039
|
}
|
|
@@ -1073,31 +1047,26 @@ export async function executeBeisenITalent(staged, session, target) {
|
|
|
1073
1047
|
catch { /* keep empty */ }
|
|
1074
1048
|
// STEP 2 — submit apply
|
|
1075
1049
|
const step2Url = `${apiRoot}/api/Apply/SubmitResume`;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
steps.push({ step: "submit", url: step2Url, status: 0, ok: false, message: String(err) });
|
|
1092
|
-
return { ok: false, posted_to: step2Url, message: "step 2 network error", steps };
|
|
1093
|
-
}
|
|
1094
|
-
const text2 = (await r2.text()).slice(0, 4000);
|
|
1095
|
-
steps.push({ step: "submit", url: step2Url, status: r2.status, ok: r2.ok, message: text2.slice(0, 200) });
|
|
1050
|
+
const s2 = await doStep("submit", step2Url, {
|
|
1051
|
+
method: "POST",
|
|
1052
|
+
headers: { ...sessionHeaders, "Content-Type": "application/json" },
|
|
1053
|
+
body: JSON.stringify({
|
|
1054
|
+
JobAdId: staged.post_id,
|
|
1055
|
+
ResumeId: resumeId,
|
|
1056
|
+
Name: applicant.name,
|
|
1057
|
+
Email: applicant.email,
|
|
1058
|
+
Mobile: applicant.phone,
|
|
1059
|
+
}),
|
|
1060
|
+
}, steps);
|
|
1061
|
+
if (!s2.ok) {
|
|
1062
|
+
return { ok: false, posted_to: step2Url, status: s2.status, message: `step 2 failed: ${s2.message}`, steps };
|
|
1063
|
+
}
|
|
1064
|
+
const r2 = s2.response;
|
|
1096
1065
|
return {
|
|
1097
1066
|
ok: r2.ok,
|
|
1098
1067
|
status: r2.status,
|
|
1099
1068
|
posted_to: step2Url,
|
|
1100
|
-
response_preview:
|
|
1069
|
+
response_preview: s2.text,
|
|
1101
1070
|
message: r2.ok ? "Beisen iTalent submission accepted" : `step 2 rejected: HTTP ${r2.status}`,
|
|
1102
1071
|
steps,
|
|
1103
1072
|
};
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ import { createRequire as require_createRequire } from "node:module";
|
|
|
60
60
|
function require_module() {
|
|
61
61
|
return { createRequire: require_createRequire };
|
|
62
62
|
}
|
|
63
|
-
const VERSION = "1.0.
|
|
63
|
+
const VERSION = "1.0.7";
|
|
64
64
|
const COMPANIES = [
|
|
65
65
|
{ key: "tencent", family: "Bespoke", source: "join.qq.com", label: "Tencent / 腾讯" },
|
|
66
66
|
{ key: "bytedance", family: "Bespoke", source: "jobs.bytedance.com", label: "ByteDance / 字节跳动" },
|
|
@@ -162,6 +162,7 @@ VERBS (same surface for every company)
|
|
|
162
162
|
--print-form emit a fillable JSON template
|
|
163
163
|
--form-file <path> merge per-job answers
|
|
164
164
|
--interactive prompt for unanswered fields
|
|
165
|
+
--batch <file|-> apply to many post_ids (one/line)
|
|
165
166
|
--debug-submit-to <url> verify wire format
|
|
166
167
|
--really-submit actually fire (env-gated)
|
|
167
168
|
memory list | get <k> | set k=v | event <kind> [payload] | clear
|
|
@@ -411,15 +412,94 @@ async function runCompany(adapter, company, rawArgs) {
|
|
|
411
412
|
return emit(adapter.checkResume(text), compact);
|
|
412
413
|
}
|
|
413
414
|
if (verb === "apply") {
|
|
414
|
-
const postId = args[0];
|
|
415
|
-
if (!postId)
|
|
416
|
-
die(`usage: job-pro ${company} apply <post_id> [--print-form | --form-file <path>] [--dry-run | --debug-submit-to <url> | --really-submit]`);
|
|
417
415
|
const reallySubmit = args.includes("--really-submit");
|
|
418
416
|
const printForm = args.includes("--print-form");
|
|
419
417
|
const interactive = args.includes("--interactive");
|
|
420
418
|
const { args: aDebug, value: debugUrl } = popFlagValue(args, "--debug-submit-to");
|
|
421
|
-
const { args:
|
|
422
|
-
|
|
419
|
+
const { args: aForm, value: formFilePath } = popFlagValue(aDebug, "--form-file");
|
|
420
|
+
const { args: aBatch, value: batchPath } = popFlagValue(aForm, "--batch");
|
|
421
|
+
// Batch mode: read post_ids from a file (or stdin if "-"). Each non-empty,
|
|
422
|
+
// non-`#`-prefixed line is a post_id. Output is a JSON array of
|
|
423
|
+
// { post_id, result } so downstream tooling can iterate.
|
|
424
|
+
if (batchPath) {
|
|
425
|
+
if (reallySubmit) {
|
|
426
|
+
die(`--batch + --really-submit is intentionally refused. Submitting to ` +
|
|
427
|
+
`multiple jobs at once is the exact failure mode this CLI is designed to ` +
|
|
428
|
+
`prevent. Drop --really-submit and use --debug-submit-to <url> for batch ` +
|
|
429
|
+
`verification, or run apply one job at a time.`);
|
|
430
|
+
}
|
|
431
|
+
let rawLines;
|
|
432
|
+
try {
|
|
433
|
+
rawLines = batchPath === "-" ? readFileSync(0, "utf8") : readFileSync(batchPath, "utf8");
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
die(`could not read batch file ${batchPath}: ${err instanceof Error ? err.message : err}`);
|
|
437
|
+
}
|
|
438
|
+
const postIds = rawLines
|
|
439
|
+
.split("\n")
|
|
440
|
+
.map((l) => l.trim())
|
|
441
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
442
|
+
if (postIds.length === 0)
|
|
443
|
+
die(`batch file ${batchPath} contains no post_ids`);
|
|
444
|
+
// We need the schema fetcher / profile / session ONCE, not per-job.
|
|
445
|
+
const fetchSchema = adapter.fetchApplicationSchema;
|
|
446
|
+
if (typeof fetchSchema !== "function") {
|
|
447
|
+
return emit({ ok: false, source: company, message: `apply: not wired for "${company}"` }, compact);
|
|
448
|
+
}
|
|
449
|
+
const prof = loadProfile();
|
|
450
|
+
if (!prof.ok)
|
|
451
|
+
die(prof.message);
|
|
452
|
+
let effectiveProfile = prof.profile;
|
|
453
|
+
if (formFilePath) {
|
|
454
|
+
const merged = applyFormFile(effectiveProfile, formFilePath);
|
|
455
|
+
if (!merged.ok)
|
|
456
|
+
die(merged.message);
|
|
457
|
+
effectiveProfile = merged.profile;
|
|
458
|
+
}
|
|
459
|
+
const session = loadSession(company);
|
|
460
|
+
const out = [];
|
|
461
|
+
for (const id of postIds) {
|
|
462
|
+
try {
|
|
463
|
+
const schemaResult = (await fetchSchema.call(adapter, id));
|
|
464
|
+
if (!schemaResult.ok || !schemaResult.schema) {
|
|
465
|
+
out.push({ post_id: id, ok: false, message: schemaResult.message ?? "schema fetch failed" });
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const staged = stageApplication(schemaResult.schema, effectiveProfile);
|
|
469
|
+
if (debugUrl) {
|
|
470
|
+
const kind = schemaResult.schema.submit_kind ?? "multipart-anon";
|
|
471
|
+
const debugExecutor = kind === "feishu-3-step" ? executeFeishu3Step :
|
|
472
|
+
kind === "moka-aes" ? executeMokaApply :
|
|
473
|
+
kind === "beisen-wecruit" ? executeBeisenWecruit :
|
|
474
|
+
kind === "beisen-italent" ? executeBeisenITalent :
|
|
475
|
+
kind === "cdp-real-browser" ? executeCdpRealBrowser :
|
|
476
|
+
null;
|
|
477
|
+
const result = debugExecutor
|
|
478
|
+
? await debugExecutor(staged, session, { kind: "debug", url: debugUrl })
|
|
479
|
+
: await submitApplication(staged, { kind: "debug", url: debugUrl });
|
|
480
|
+
out.push({ post_id: id, ok: result.ok, ready: staged.ready, submit_kind: kind, debug_result: result });
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
out.push({
|
|
484
|
+
post_id: id,
|
|
485
|
+
ok: staged.ready,
|
|
486
|
+
ready: staged.ready,
|
|
487
|
+
submit_kind: schemaResult.schema.submit_kind,
|
|
488
|
+
message: staged.ready ? "staged ok" : `${staged.unanswered_required.length} required field(s) unfilled`,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch (err) {
|
|
493
|
+
out.push({ post_id: id, ok: false, message: err instanceof Error ? err.message : String(err) });
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const okCount = out.filter((r) => r.ok).length;
|
|
497
|
+
return emit({ mode: debugUrl ? "batch-debug" : "batch-dry-run", company, total: out.length, ok_count: okCount, results: out }, compact);
|
|
498
|
+
}
|
|
499
|
+
void aBatch;
|
|
500
|
+
const postId = args[0];
|
|
501
|
+
if (!postId)
|
|
502
|
+
die(`usage: job-pro ${company} apply <post_id> [--print-form | --form-file <path> | --interactive | --batch <file>] [--debug-submit-to <url> | --really-submit]`);
|
|
423
503
|
const fetchSchema = adapter.fetchApplicationSchema;
|
|
424
504
|
if (typeof fetchSchema !== "function") {
|
|
425
505
|
return emit({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Query Chinese big-tech campus recruiting from your terminal. 50 companies, all 50 live. 46 via each company's own API; the 4 with no public canonical feed (Hikvision, CICC, Cainiao, WeBank) surfaced via Liepin as a clearly-labeled third-party fallback. No signup, no token, no server.",
|
|
5
5
|
"homepage": "https://job.ha7ch.com",
|
|
6
6
|
"repository": "https://github.com/HA7CH/job-pro",
|