// swissdevjobs.ch — RSS feed sweep (Switzerland-wide, tech only, salary disclosed). // The /api/jobs endpoint is deprecated; the RSS feed carries the full description. import { NON_ENGLISH_RE, JUNIOR_TITLE_RE, JAVA_TITLE_RE, SOFTWARE_TITLE_RE } from "../fetch.ts"; import { readFileSync } from "fs"; const csv = readFileSync("./results/jobs.csv", "utf8"); const existing = new Set([...csv.matchAll(/"(https?:\/\/[^"]+)"/g)].map((m) => m[1])); const res = await fetch("https://swissdevjobs.ch/rss", { headers: { "User-Agent": "Mozilla/5.0" } }); const xml = await res.text(); const MS_RE = /\b(\.net|c#|azure|sharepoint|dynamics|power\s?(bi|platform|apps)|sccm|intune|windows|sap|abap)\b/i; const DE_FR_WORDS_RE = /\b(und|oder|wir|Sie|Ihre|Ihrer|Kenntnisse|Erfahrung|Aufgaben|Anforderungen|Stelle|Unternehmen|Mitarbeiter|Bereich|für|mit|nous|vous|votre|pour|avec|des|les|une|dans)\b/g; const items = [...xml.matchAll(/([\s\S]*?)<\/item>/g)].map((m) => m[1]); console.log(`${items.length} feed items`); const out: Array<{ title: string; company: string; salary: string; link: string; date: string; text: string }> = []; for (const item of items) { const rawTitle = item.match(/<!\[CDATA\[([\s\S]*?)\]\]><\/title>/)?.[1] ?? ""; const link = (item.match(/<link>([\s\S]*?)<\/link>/)?.[1] ?? "").split("?")[0]; const date = item.match(/<pubDate>([\s\S]*?)<\/pubDate>/)?.[1] ?? ""; const desc = item.match(/<description><!\[CDATA\[([\s\S]*?)\]\]><\/description>/)?.[1] ?? ""; // "Title @ Company [CHF x - y]" const m = rawTitle.match(/^(.*?)\s+@\s+(.*?)(?:\s+\[(.*?)\])?$/); const title = (m?.[1] ?? rawTitle).trim(); const company = (m?.[2] ?? "").trim(); const salary = (m?.[3] ?? "").trim(); if (NON_ENGLISH_RE.test(title) || JUNIOR_TITLE_RE.test(title) || JAVA_TITLE_RE.test(title) || !SOFTWARE_TITLE_RE.test(title)) continue; if (MS_RE.test(title)) continue; if (existing.has(link)) continue; const text = desc.replace(/<[^>]+>/g, " ").replace(/\s+/g, " "); if ((text.match(DE_FR_WORDS_RE) ?? []).length >= 6) continue; out.push({ title, company, salary, link, date, text }); } console.log(`\n${out.length} candidates — fetching locations…\n`); for (const j of out) { let loc = ""; try { const page = await fetch(j.link, { headers: { "User-Agent": "Mozilla/5.0" } }); const html = await page.text(); // The SPA shell carries no JSON-LD, but the meta description reads // "Become <title> at <company> in <City>. …" loc = html.match(/<meta name="description" content="[^"]*?\sin\s([^".]+)\./)?.[1]?.trim() ?? ""; await Bun.sleep(300); } catch { /* leave blank */ } console.log(`${j.title} | ${j.company} | ${loc || "?"} | ${j.salary} | ${j.date.slice(5, 16)}`); console.log(` ${j.link}`); }