| |
| |
| |
| |
|
|
| (function () { |
| "use strict"; |
|
|
| |
| const jobData = detectJobPosting(); |
| if (jobData) { |
| chrome.runtime.sendMessage({ type: "DETECT_JOB", data: jobData }); |
| } |
|
|
| |
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { |
| if (message.type === "EXTRACT_JOB") { |
| const data = extractJobData(); |
| sendResponse({ data }); |
| return true; |
| } |
| }); |
|
|
| function detectJobPosting() { |
| |
| const jsonLd = extractJsonLd(); |
| if (jsonLd) return jsonLd; |
|
|
| |
| 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" }; |
| } |
|
|
| |
| const ogType = document.querySelector('meta[property="og:type"]'); |
| if (ogType && ogType.content === "article") { |
| |
| 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) { |
| |
| } |
| } |
| 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() { |
| |
| const jsonLd = extractJsonLd(); |
| if (jsonLd && jsonLd.title) return jsonLd; |
|
|
| |
| const data = { |
| detected: true, |
| source: "heuristic", |
| title: "", |
| company: "", |
| location: "", |
| description: "", |
| apply_url: window.location.href, |
| }; |
|
|
| |
| 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; |
| } |
| } |
|
|
| |
| 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; |
| } |
| } |
|
|
| |
| 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; |
| } |
| } |
|
|
| |
| 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; |
| } |
| })(); |
|
|