jd-intel 0.3.0 → 0.3.2
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/registry/teamtailor.json +2 -1
- package/src/adapters/teamtailor.js +29 -11
- package/src/normalizer.js +6 -3
package/package.json
CHANGED
package/registry/teamtailor.json
CHANGED
|
@@ -13,5 +13,6 @@
|
|
|
13
13
|
{"slug": "billogram", "name": "Billogram", "sector": "fintech / billing"},
|
|
14
14
|
{"slug": "detectify", "name": "Detectify", "sector": "security"},
|
|
15
15
|
{"slug": "storytel", "name": "Storytel", "sector": "audiobooks"},
|
|
16
|
-
{"slug": "oneflow", "name": "Oneflow", "sector": "contract management"}
|
|
16
|
+
{"slug": "oneflow", "name": "Oneflow", "sector": "contract management"},
|
|
17
|
+
{"slug": "crunchbase", "name": "Crunchbase", "sector": "company data"}
|
|
17
18
|
]
|
|
@@ -23,14 +23,36 @@ import { normalize, stripHtml } from '../normalizer.js';
|
|
|
23
23
|
* @param {string} slug - TeamTailor career-site slug (e.g., 'tibber')
|
|
24
24
|
* @returns {Promise<Array>} Normalized job objects
|
|
25
25
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// Most sites are {slug}.teamtailor.com, but some sit on a regional
|
|
27
|
+
// segment, e.g. crunchbase.na.teamtailor.com. '' is the base host.
|
|
28
|
+
const TT_REGIONS = ['', 'na', 'eu'];
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Resolve which TeamTailor host actually serves this slug's feed.
|
|
32
|
+
* Returns the first 200 Response, throws on a non-404 error, or
|
|
33
|
+
* returns null if no region has a feed.
|
|
34
|
+
*/
|
|
35
|
+
async function resolveFeed(slug, method = 'GET') {
|
|
36
|
+
for (const region of TT_REGIONS) {
|
|
37
|
+
const host = region
|
|
38
|
+
? `${slug}.${region}.teamtailor.com`
|
|
39
|
+
: `${slug}.teamtailor.com`;
|
|
40
|
+
const resp = await fetch(`https://${host}/jobs.rss`, {
|
|
41
|
+
method,
|
|
42
|
+
redirect: 'follow',
|
|
43
|
+
});
|
|
44
|
+
if (resp.ok) return resp;
|
|
45
|
+
if (resp.status !== 404) {
|
|
46
|
+
throw new Error(`TeamTailor RSS error for ${slug}: ${resp.status}`);
|
|
47
|
+
}
|
|
48
|
+
// 404 on this host — try the next region.
|
|
33
49
|
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function fetchTeamtailor(slug) {
|
|
54
|
+
const resp = await resolveFeed(slug, 'GET');
|
|
55
|
+
if (!resp) return []; // No TeamTailor site in any known region
|
|
34
56
|
|
|
35
57
|
const xml = await resp.text();
|
|
36
58
|
|
|
@@ -105,11 +127,7 @@ function decodeEntities(s) {
|
|
|
105
127
|
*/
|
|
106
128
|
export async function hasTeamtailor(slug) {
|
|
107
129
|
try {
|
|
108
|
-
|
|
109
|
-
method: 'HEAD',
|
|
110
|
-
redirect: 'follow',
|
|
111
|
-
});
|
|
112
|
-
return resp.ok;
|
|
130
|
+
return (await resolveFeed(slug, 'HEAD')) !== null;
|
|
113
131
|
} catch {
|
|
114
132
|
return false;
|
|
115
133
|
}
|
package/src/normalizer.js
CHANGED
|
@@ -3,8 +3,11 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
/**
|
|
4
4
|
* Generate a stable ID for a job posting.
|
|
5
5
|
*/
|
|
6
|
-
export function jobId(company, title, ats) {
|
|
7
|
-
|
|
6
|
+
export function jobId(company, title, ats, location = '') {
|
|
7
|
+
// Location is part of identity: the same role posted in multiple
|
|
8
|
+
// offices is distinct requisitions with distinct URLs. Omitting it
|
|
9
|
+
// collapsed multi-office postings to one id (see issue #17).
|
|
10
|
+
const raw = `${company}|${title}|${ats}|${location}`.toLowerCase().trim();
|
|
8
11
|
return createHash('md5').update(raw).digest('hex').substring(0, 12);
|
|
9
12
|
}
|
|
10
13
|
|
|
@@ -14,7 +17,7 @@ export function jobId(company, title, ats) {
|
|
|
14
17
|
export function normalize(raw, ats) {
|
|
15
18
|
const now = new Date().toISOString();
|
|
16
19
|
return {
|
|
17
|
-
id: jobId(raw.company || raw.companySlug, raw.title, ats),
|
|
20
|
+
id: jobId(raw.company || raw.companySlug, raw.title, ats, raw.location || ''),
|
|
18
21
|
company: raw.company || raw.companySlug || '',
|
|
19
22
|
companySlug: raw.companySlug || '',
|
|
20
23
|
ats,
|