best / extension /src /content /detector.js
anky2002's picture
feat: Add Job Alerts, Admin Dashboard, Semantic Search (pgvector), Interview Prep Agent, Chrome Extension
3d3d997 verified
Raw
History Blame Contribute Delete
5.6 kB
/**
* 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;
}
})();