Last active
March 7, 2025 11:50
-
-
Save andywerner/bad051c75f841763f7f67bfc2c258dac to your computer and use it in GitHub Desktop.
JS to screenshot details page of a JobNinja job details page (liquid only) - Fast n dirty ChatGPT session. But quite cool!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 📌 JobNinja Job Screenshot Script | |
* ✅ Based on `dom-to-image`: https://github.com/tsayen/dom-to-image | |
* 📸 Captures job details and downloads them as a PNG screenshot. | |
* 🛠️ How to use: | |
* 1️⃣ Open a JobNinja job listing (e.g., https://jobninja.com/jobs/information-manager-legal-german-language-required-f-m-d--10980089) | |
* 2️⃣ Open Developer Tools (`F12` or `Ctrl+Shift+I`) → Console tab. | |
* 3️⃣ Paste this script into the console and press Enter. | |
* 4️⃣ Wait a few seconds ⏳. The screenshot appears on the page and downloads automatically. | |
*/ | |
(async () => { | |
// ✅ Step 1: Load dom-to-image dynamically if not already available | |
const script = document.createElement("script"); | |
script.src = "https://cdnjs.cloudflare.com/ajax/libs/dom-to-image/2.6.0/dom-to-image.min.js"; | |
script.onload = async () => { | |
console.log("✅ dom-to-image loaded!"); | |
// ✅ Step 2: Select the job detail container(s) | |
const targetElements = document.querySelectorAll('div[class^="JobSearch_jobDetail_"]'); | |
if (!targetElements.length) { | |
console.error("❌ No matching elements found!"); | |
return; | |
} | |
// ✅ Step 3: Create a wrapper for selected elements | |
const wrapper = document.createElement("div"); | |
wrapper.style.backgroundColor = "white"; // Ensures a white background | |
wrapper.style.padding = "10px"; // Adds padding for clean visuals | |
wrapper.style.display = "inline-block"; // Prevents full-page expansion | |
// ✅ Step 4: Clone and copy styles for the job elements | |
targetElements.forEach(el => { | |
const clonedEl = el.cloneNode(true); | |
copyComputedStyles(el, clonedEl); | |
wrapper.appendChild(clonedEl); | |
}); | |
// Temporarily add wrapper to the page | |
document.body.appendChild(wrapper); | |
console.log("📌 Cloned Elements:", wrapper.innerHTML); | |
// ✅ Step 5: Load and replace images before rendering | |
await loadAndReplaceImages(wrapper); | |
try { | |
console.log("⏳ Converting elements to PNG..."); | |
// ✅ Step 6: Convert the wrapper to a PNG image | |
const dataUrl = await domtoimage.toPng(wrapper, { | |
bgcolor: "white", // White background in the PNG | |
filter: node => { | |
// ✅ Keep text nodes | |
if (node.nodeType === 3) return true; | |
// ✅ Ignore non-elements (e.g., comments) | |
if (!(node instanceof Element)) return false; | |
// ❌ Remove unnecessary elements (iframes, scripts, tracking pixels) | |
if (["IFRAME", "SCRIPT", "LINK"].includes(node.tagName)) return false; | |
// ❌ Skip tiny tracking pixels | |
if (node.tagName === "IMG" && (node.width <= 2 && node.height <= 2)) { | |
console.warn(`⚠️ Skipping tiny tracking pixel: ${node.src}`); | |
return false; | |
} | |
// ❌ Skip cross-origin images (to avoid CORS errors) | |
if (node.tagName === "IMG") { | |
try { | |
const imageURL = new URL(node.src); | |
if (imageURL.origin !== location.origin) { | |
console.warn(`⚠️ Skipping CORS image: ${node.src}`); | |
return false; | |
} | |
} catch (e) { | |
console.warn(`⚠️ Skipping invalid image URL: ${node.src}`, e); | |
return false; | |
} | |
} | |
return true; // ✅ Keep all other content | |
} | |
}); | |
console.log("✅ Image successfully created!"); | |
// ✅ Step 7: Remove the wrapper after capturing | |
document.body.removeChild(wrapper); | |
// ✅ Step 8: Display the image on the page | |
const img = new Image(); | |
img.src = dataUrl; | |
document.body.appendChild(img); | |
console.log("🖼️ Image added to the page!"); | |
// ✅ Step 9: Download the image automatically | |
const link = document.createElement("a"); | |
link.href = dataUrl; | |
link.download = "screenshot.png"; | |
link.click(); | |
console.log("📥 Download triggered!"); | |
} catch (error) { | |
console.error("❌ Error converting elements:", error); | |
} | |
}; | |
document.body.appendChild(script); | |
// ✅ Function: Copy Computed Styles | |
function copyComputedStyles(source, target) { | |
const computedStyle = window.getComputedStyle(source); | |
for (let i = 0; i < computedStyle.length; i++) { | |
const prop = computedStyle[i]; | |
target.style[prop] = computedStyle.getPropertyValue(prop); | |
} | |
[...source.children].forEach((child, index) => { | |
if (target.children[index]) { | |
copyComputedStyles(child, target.children[index]); | |
} | |
}); | |
} | |
// ✅ Function: Load and Replace Images | |
async function loadAndReplaceImages(container) { | |
const images = container.querySelectorAll("img"); | |
for (let img of images) { | |
const originalSrc = img.src; | |
if (!originalSrc) continue; | |
// ✅ Skip Tracking Pixels | |
if ((img.width <= 2 && img.height <= 2) || img.src.includes("tracking") || img.src.includes("beacon")) { | |
console.warn(`⚠️ Skipping tracking pixel: ${originalSrc}`); | |
continue; | |
} | |
// ✅ Skip CORS-restricted images | |
try { | |
const imageURL = new URL(originalSrc); | |
if (imageURL.origin !== location.origin) { | |
console.warn(`⚠️ Skipping external image due to CORS: ${originalSrc}`); | |
continue; | |
} | |
} catch (e) { | |
console.warn(`⚠️ Skipping invalid image URL: ${originalSrc}`, e); | |
continue; | |
} | |
try { | |
// ✅ Load image first | |
const loadedImg = await loadImage(originalSrc); | |
// ✅ Replace with simple `<img>` element | |
const simpleImg = document.createElement("img"); | |
simpleImg.src = loadedImg.src; | |
simpleImg.width = img.width; | |
simpleImg.height = img.height; | |
simpleImg.style = img.style.cssText; | |
img.replaceWith(simpleImg); | |
console.log(`🔄 Replaced image: ${originalSrc}`); | |
} catch (error) { | |
console.warn(`⚠️ Failed to load image: ${originalSrc}`, error); | |
} | |
} | |
} | |
// ✅ Helper Function: Load an Image | |
function loadImage(url) { | |
return new Promise((resolve, reject) => { | |
const img = new Image(); | |
img.crossOrigin = "anonymous"; // Allow cross-origin images to be used in canvas | |
img.src = url; | |
img.onload = () => resolve(img); | |
img.onerror = () => reject(`Image failed to load: ${url}`); | |
}); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment