crawlforge-extractors 1.5.0 → 1.5.1
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/package.json +1 -1
- package/src/connectors/ats.js +18 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlforge-extractors",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Extraction logic shared by the CrawlForge MCP server and REST API — scrape templates, charset-correct capped body reading, structural fingerprinting, and embedded-state extraction. One implementation, so the two surfaces cannot drift apart.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
package/src/connectors/ats.js
CHANGED
|
@@ -652,7 +652,10 @@ export const ATS_TEMPLATES = [
|
|
|
652
652
|
'Read a company\'s published Teamtailor jobs from the careers site\'s documented RSS feed ' +
|
|
653
653
|
'rather than the rendered page: title, department, locations, remote status and plain-text ' +
|
|
654
654
|
'description for every open role. The feed returns the first 100 jobs unless per_page says otherwise.',
|
|
655
|
-
|
|
655
|
+
// The careers-site root counts as a target: it is what a user pastes, and
|
|
656
|
+
// resolveUrl turns it into <host>/jobs.rss. A deeper path (/jobs/internal/,
|
|
657
|
+
// which robots disallows anyway) still does not match.
|
|
658
|
+
targetPattern: /teamtailor\.com\/?(?:jobs(\.rss)?\/?)?(\?|#|$)/i,
|
|
656
659
|
|
|
657
660
|
/** `company` is the subdomain in https://<company>.teamtailor.com. */
|
|
658
661
|
listUrl(params = {}) {
|
|
@@ -671,7 +674,13 @@ export const ATS_TEMPLATES = [
|
|
|
671
674
|
resolveUrl(url) {
|
|
672
675
|
const parsed = new URL(url);
|
|
673
676
|
if (parsed.pathname.endsWith('.rss')) return url;
|
|
674
|
-
|
|
677
|
+
// The feed is the JOBS page with ".rss" appended, so a careers-site root
|
|
678
|
+
// has to gain the jobs path first. Stripping the trailing slash off "/"
|
|
679
|
+
// leaves "", which built "<host>/.rss" — a URL Teamtailor answers with
|
|
680
|
+
// 403 on every tenant tested. The bare root is what a user actually
|
|
681
|
+
// pastes, so it has to resolve to the documented "<host>/jobs.rss".
|
|
682
|
+
const path = parsed.pathname.replace(/\/+$/, '');
|
|
683
|
+
parsed.pathname = `${path || '/jobs'}.rss`;
|
|
675
684
|
return parsed.toString();
|
|
676
685
|
},
|
|
677
686
|
|
|
@@ -681,9 +690,14 @@ export const ATS_TEMPLATES = [
|
|
|
681
690
|
const $ = load(body, { xmlMode: true });
|
|
682
691
|
const items = $('channel > item');
|
|
683
692
|
|
|
684
|
-
|
|
693
|
+
// Only a response that is not a feed is an error. A valid feed with no
|
|
694
|
+
// <item> is a company with nothing open right now — normative.teamtailor.com
|
|
695
|
+
// serves exactly that — and every sibling ATS connector reports an empty
|
|
696
|
+
// board as count: 0 rather than throwing. Conflating the two turned
|
|
697
|
+
// "nobody is hiring" into "the tool is broken".
|
|
698
|
+
if (!$('rss').length) {
|
|
685
699
|
throw new Error(
|
|
686
|
-
`No Teamtailor job feed at ${url}:
|
|
700
|
+
`No Teamtailor job feed at ${url}: the response is not an RSS feed. ` +
|
|
687
701
|
'The feed is the careers site jobs page with ".rss" appended, e.g. ' +
|
|
688
702
|
'https://<company>.teamtailor.com/jobs.rss.'
|
|
689
703
|
);
|