Files
Jobber/extractors/careerspages/manifest.ts
T
ilia 84e6835b11
CI / skip-ci-check (push) Successful in 10s
CI / secret-scan (push) Successful in 14s
CI / docker-ci (push) Successful in 17s
feat: keyword sets, sponsorship signals, and extractor/profile fixes
Add per-profile keyword sets, job source settings, sponsorship signal pills,
and several ATS extractors. Fix Hiring Cafe discovery via Next.js SSR search,
profile activate for comma-separated basicAuthUser aliases, and resume path
backfill migrations. Update settings and hiring-cafe docs; localhost compose
overlay for loopback-only deploys.
2026-06-11 10:23:39 -04:00

90 lines
2.6 KiB
TypeScript

/**
* Direct company career pages — auto-detect ATS backends and pull public job feeds.
*/
import type {
ExtractorManifest,
ExtractorRunResult,
} from "@shared/types/extractors";
import type { CreateJobInput } from "@shared/types/jobs";
import {
detectAtsTargets,
fetchCareersPage,
fetchJobsForTarget,
readUrlList,
} from "./src/resolve-ats.js";
function matchesTerm(job: CreateJobInput, term: string): boolean {
const lower = term.toLowerCase();
if (job.title.toLowerCase().includes(lower)) return true;
if (job.employer.toLowerCase().includes(lower)) return true;
if (job.jobDescription?.toLowerCase().includes(lower)) return true;
return false;
}
export const manifest: ExtractorManifest = {
id: "careerspages",
displayName: "Career Pages",
providesSources: ["careerspages"],
async run(context): Promise<ExtractorRunResult> {
if (context.shouldCancel?.()) return { success: true, jobs: [] };
const pageUrls = readUrlList(context.settings.careersPageUrls);
if (pageUrls.length === 0) {
return {
success: true,
jobs: [],
error:
"No career page URLs configured. Set CAREERS_PAGE_URLS or careersPageUrls (JSON array or comma/newline-separated URLs).",
};
}
const terms = context.searchTerms.length > 0 ? context.searchTerms : [];
const seen = new Set<string>();
const out: CreateJobInput[] = [];
try {
for (let i = 0; i < pageUrls.length; i += 1) {
if (context.shouldCancel?.()) break;
const pageUrl = pageUrls[i];
context.onProgress?.({
phase: "list",
termsProcessed: i,
termsTotal: pageUrls.length,
currentUrl: pageUrl,
detail: `Career pages: resolving ATS for ${pageUrl}`,
});
const html = await fetchCareersPage(pageUrl);
const targets = detectAtsTargets(html, pageUrl);
if (targets.length === 0) continue;
for (const target of targets) {
if (context.shouldCancel?.()) break;
const jobs = await fetchJobsForTarget(target);
for (const job of jobs) {
if (
terms.length > 0 &&
!terms.some((term) => matchesTerm(job, term))
) {
continue;
}
const key = job.jobUrl;
if (seen.has(key)) continue;
seen.add(key);
out.push(job);
}
}
}
return { success: true, jobs: out };
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return { success: false, jobs: out, error: message };
}
},
};
export default manifest;