/** * Content script - Detects job postings on the current page. * Extracts schema.org/JSON-LD data and common ATS page patterns. */ (function () { "use strict"; // Detect if current page has a job posting const jobData = detectJobPosting(); if (jobData) { chrome.runtime.sendMessage({ type: "DETECT_JOB", data: jobData }); } // Listen for extraction requests from background chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "EXTRACT_JOB") { const data = extractJobData(); sendResponse({ data }); return true; } }); function detectJobPosting() { // 1. Check JSON-LD const jsonLd = extractJsonLd(); if (jsonLd) return jsonLd; // 2. Check common ATS URL patterns const url = window.location.href.toLowerCase(); const isJobPage = url.includes("/jobs/") || url.includes("/careers/") || url.includes("/positions/") || url.includes("/job/") || url.includes("greenhouse.io") || url.includes("lever.co") || url.includes("myworkdayjobs.com") || url.includes("smartrecruiters.com") || url.includes("ashbyhq.com"); if (isJobPage) { return { detected: true, source: "url_pattern" }; } // 3. Check meta tags const ogType = document.querySelector('meta[property="og:type"]'); if (ogType && ogType.content === "article") { // Could be a job posting page const title = document.title.toLowerCase(); if ( title.includes("job") || title.includes("career") || title.includes("position") || title.includes("hiring") ) { return { detected: true, source: "meta_tags" }; } } return null; } function extractJsonLd() { const scripts = document.querySelectorAll('script[type="application/ld+json"]'); for (const script of scripts) { try { const data = JSON.parse(script.textContent); if (data["@type"] === "JobPosting") { return normalizeJobPosting(data); } if (Array.isArray(data)) { for (const item of data) { if (item["@type"] === "JobPosting") { return normalizeJobPosting(item); } } } if (data["@graph"]) { for (const item of data["@graph"]) { if (item["@type"] === "JobPosting") { return normalizeJobPosting(item); } } } } catch (e) { // Invalid JSON } } return null; } function normalizeJobPosting(data) { const location = data.jobLocation; let locationStr = ""; if (location) { if (typeof location === "string") { locationStr = location; } else if (location.address) { const addr = location.address; locationStr = [addr.addressLocality, addr.addressRegion, addr.addressCountry] .filter(Boolean) .join(", "); } } return { detected: true, source: "json_ld", title: data.title || "", company: data.hiringOrganization?.name || "", location: locationStr, description: data.description || "", employment_type: data.employmentType || "", salary: data.baseSalary || null, date_posted: data.datePosted || "", apply_url: data.url || window.location.href, remote: data.jobLocationType === "TELECOMMUTE", }; } function extractJobData() { // Try JSON-LD first const jsonLd = extractJsonLd(); if (jsonLd && jsonLd.title) return jsonLd; // Fallback: heuristic extraction from page content const data = { detected: true, source: "heuristic", title: "", company: "", location: "", description: "", apply_url: window.location.href, }; // Try to get title from common selectors const titleSelectors = [ 'h1[class*="job"]', 'h1[class*="title"]', 'h1[data-qa*="job"]', ".job-title", ".posting-headline h2", '[data-automation="job-detail-title"]', "h1", ]; for (const sel of titleSelectors) { const el = document.querySelector(sel); if (el && el.textContent.trim().length > 3) { data.title = el.textContent.trim(); break; } } // Company name const companySelectors = [ '[class*="company-name"]', '[data-qa*="company"]', ".company-name", 'meta[property="og:site_name"]', ]; for (const sel of companySelectors) { const el = document.querySelector(sel); if (el) { data.company = el.content || el.textContent?.trim() || ""; if (data.company) break; } } // Location const locationSelectors = [ '[class*="location"]', '[data-qa*="location"]', ".job-location", ]; for (const sel of locationSelectors) { const el = document.querySelector(sel); if (el && el.textContent.trim()) { data.location = el.textContent.trim(); break; } } // Description - get the main content area const descSelectors = [ '[class*="job-description"]', '[class*="description"]', '[data-qa*="description"]', ".content-payload", "article", 'main [class*="content"]', ]; for (const sel of descSelectors) { const el = document.querySelector(sel); if (el && el.textContent.trim().length > 100) { data.description = el.textContent.trim().substring(0, 5000); break; } } return data; } })();