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
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bun
// Ruby/Rails sweep: Switzerland-wide (onsite+hybrid) + Switzerland remote + EU remote.
// Title-keyword search is too narrow for Ruby — most Rails roles are titled
// "Backend Engineer" — so this also runs a description-level Ruby check over
// generic backend/fullstack titles.
import { fetchJobs, isEnglishContent, NON_ENGLISH_RE, JUNIOR_TITLE_RE } from "../fetch.ts";
import type { SearchConfig } from "../config.ts";
const RUBY_KEYWORDS = [
"Ruby on Rails", "Rails Engineer", "Ruby Engineer", "Rails Developer",
"Ruby Developer", "Ruby", "Rails",
].join(" OR ");
const BACKEND_KEYWORDS = [
"Backend Engineer", "Full Stack Engineer", "Fullstack Developer",
"Senior Software Engineer", "Tech Lead", "Staff Engineer",
].join(" OR ");
const RUBY_RE = /\bruby\b|\brails\b|\bsidekiq\b|\bhotwire\b|\bactiverecord\b|\bactive record\b/i;
const MS_RE = /\b(\.net|c#|sharepoint|dynamics|sccm|intune)\b/i;
const csv = await Bun.file("./results/jobs.csv").text();
const existing = new Set([...csv.matchAll(/"(https?:\/\/[^"]+)"/g)].map((m) => m[1]));
const searches: Array<SearchConfig & { descCheck?: boolean }> = [
{ label: "CH — Ruby titles", keywords: RUBY_KEYWORDS, location: "Switzerland", workType: ["onsite", "hybrid"], timePosted: "month" },
{ label: "CH remote — Ruby titles", keywords: RUBY_KEYWORDS, location: "Switzerland", workType: ["remote"], timePosted: "month" },
{ label: "Basel — Ruby titles", keywords: RUBY_KEYWORDS, geoId: "90009885", location: "Basel Metropolitan Area", workType: ["onsite", "hybrid"], timePosted: "month" },
{ label: "Zürich — Ruby titles", keywords: RUBY_KEYWORDS, geoId: "90009888", location: "Zürich Area", workType: ["onsite", "hybrid"], timePosted: "month" },
{ label: "CH — backend (desc check)", keywords: BACKEND_KEYWORDS, location: "Switzerland", workType: ["onsite", "hybrid", "remote"], timePosted: "month", descCheck: true },
];
type Hit = { title: string; company: string; location: string; link: string; label: string; via: string };
const hits: Hit[] = [];
const seen = new Set<string>();
for (const config of searches) {
console.log(`\n[${config.label}]`);
let jobs;
try {
jobs = await fetchJobs(config, { raw: true });
} catch (e) {
console.log(` error: ${e}`);
continue;
}
console.log(` ${jobs.length} raw cards`);
for (const j of jobs) {
if (seen.has(j.link)) continue;
if (NON_ENGLISH_RE.test(j.title) || JUNIOR_TITLE_RE.test(j.title)) continue;
if (MS_RE.test(j.title)) continue;
const titleRuby = RUBY_RE.test(j.title);
if (!titleRuby && !config.descCheck) continue;
// Confirm Ruby in the description (and that the posting is in English).
let bodyRuby = false;
try {
const res = await fetch(`https://www.linkedin.com/jobs/view/${j.id}/`, {
headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" },
});
const html = await res.text();
const body = (html.match(/<div class="show-more-less-html__markup[^"]*"[^>]*>([\s\S]*?)<\/div>/)?.[1] ?? "")
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
bodyRuby = RUBY_RE.test(body);
await Bun.sleep(350);
} catch { /* fall back to the title signal */ }
if (!titleRuby && !bodyRuby) continue;
if (!(await isEnglishContent(j.id))) {
console.log(` dropped (non-English): ${j.title}`);
continue;
}
seen.add(j.link);
hits.push({
title: j.title, company: j.company, location: (j as any).location ?? "?",
link: j.link, label: config.label,
via: titleRuby ? (bodyRuby ? "title+desc" : "title") : "desc",
});
console.log(` HIT ${existing.has(j.link) ? "(already in CSV)" : "(NEW)"}: ${j.title} | ${j.company}`);
}
}
console.log(`\n=== ${hits.length} RUBY/RAILS HITS ===\n`);
for (const h of hits) {
console.log(`${existing.has(h.link) ? "[seen] " : "[NEW] "}${h.title} | ${h.company} | ${h.location} | via ${h.via} | ${h.label}`);
console.log(` ${h.link}`);
}