chore: update search scripts, extra sources and post drafts

Adds the one-off scripts accumulated across recent sessions, removes
temp scripts whose searches now live in config.ts as role-family
sweeps, and updates fetch-other.ts plus the LinkedIn post drafts.
This commit is contained in:
2026-08-27 09:03:05 +02:00
parent 3f5360f44c
commit b4957025b1
31 changed files with 2008 additions and 283 deletions
+58
View File
@@ -0,0 +1,58 @@
// 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(/<item>([\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(/<title><!\[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}`);
}