Last active
July 10, 2024 09:41
-
-
Save ikouchiha47/720d45eccfa61ef41d5262b26fca54dc to your computer and use it in GitHub Desktop.
Suck'em ratings
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
const { GoogleResults, BingResults, DDGResults } = require('./sniffratings'); | |
function parseArguments(args) { | |
const argsMap = {}; | |
let currentKey = null; | |
for (let i = 2; i < args.length; i++) { | |
const arg = args[i]; | |
if (arg.startsWith('-')) { | |
currentKey = arg.slice(1); | |
argsMap[currentKey] = true; | |
} else if (currentKey) { | |
argsMap[currentKey] = arg; | |
currentKey = null; | |
} | |
} | |
return argsMap; | |
} | |
function handleRatingSearch(company, sp) { | |
company = company.trim(); | |
if (!company) return Promise.reject('empty_content') | |
let fetcher = { getReviews: () => Promise.reject("invalid_search_provider") } | |
if (sp == 'google') { | |
fetcher = GoogleResults.init(company) | |
} else if (sp == 'ddg') { | |
fetcher = DDGResults.init(company) | |
} | |
return fetcher.getReviews(); | |
} | |
async function main() { | |
const argsMap = parseArguments(process.argv); | |
if (!argsMap.company) { | |
console.error('Error: Company name is required. Use -company option.'); | |
return; | |
} | |
const company = argsMap.company; | |
const sp = argsMap.search || 'ddg'; | |
try { | |
const ratings = await handleRatingSearch(company, sp); | |
console.log(`Ratings for ${company}:`, ratings); | |
} catch (error) { | |
console.error('Error retrieving ratings:', error); | |
} | |
} | |
main() |
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
// ==UserScript== | |
// @name RatingSniffer | |
// @version 1.1 | |
// @match https://www.linkedin.com/jobs/* | |
// @run-at document-idle | |
// ==/UserScript== | |
(function(_window) { | |
const MAX_LOOP = 10000; | |
function $(el) { | |
return document.querySelector(el) | |
} | |
var SP = 'ddg'; //google | |
function waitTill(fn, comp, cb) { | |
let loopCount = 0; | |
let interval = setInterval(() => { | |
let [values, ok] = comp(fn()); | |
console.log("running", ok, values); | |
if (ok) { | |
console.log("result received") | |
clearInterval(interval) | |
cb(null, values); | |
return | |
} | |
if (loopCount == MAX_LOOP) { | |
clearInterval(interval); | |
cb("max loop", values); | |
return | |
} | |
loopCount += 1; | |
}, 2000) | |
} | |
function getNewRater() { | |
let ratingEl = document.createElement("a"); | |
ratingEl.href = "#"; | |
ratingEl.id = "berater_el" | |
ratingEl.innerText = "GetRated" | |
ratingEl.className = "jobs-save-button artdeco-button artdeco-button--secondary artdeco-button--3" | |
ratingEl.style = "margin-left: 10px;"; | |
ratingEl.onclick = () => { | |
let companyName = $(".job-details-jobs-unified-top-card__company-name a").innerText.trim(); | |
getCompanyRatings(companyName, SP).then(res => alert(res)).catch(err => alert(err)) | |
} | |
return ratingEl; | |
} | |
function debounce(func, delay) { | |
let debounceTimer; | |
delay = delay || 2000 | |
return function() { | |
const context = this; | |
const args = arguments; | |
clearTimeout(debounceTimer); | |
debounceTimer = setTimeout(() => func.apply(context, args), delay); | |
}; | |
}; | |
const mutator = (elq) => (mutationsList, observer) => { | |
let hasMutated = false; | |
//TODO: add a debouncer | |
for (const mutation of mutationsList) { | |
if (mutation.type === 'childList' || mutation.type === 'attributes') { | |
// console.log("lala"); | |
hasMutated = true | |
break; | |
} | |
} | |
console.log("mutated?", hasMutated); | |
if (!hasMutated) return; | |
let element = $(elq); | |
let html = element.innerText; | |
//console.log("html", !html.includes("GetRated")); | |
if (hasMutated && !html.includes("GetRated")) { | |
let ratingEl = getNewRater(); | |
console.log("appending to ", elq); | |
element.appendChild(ratingEl); | |
} | |
} | |
let watcherQs = [ | |
".jobs-details__main-content--single-pane .mt5 div:first-child", | |
".job-details-jobs-unified-top-card__container--two-pane .mt5 div:first-child" | |
] | |
//fireup | |
// jobs-details__main-content jobs-details__main-content--single-pane | |
// job-details-jobs-unified-top-card__container--two-pane" | |
// | |
waitTill(() => watcherQs.slice(0, 1), | |
(values) => [values, values && values.length > 0], | |
(err, elements) => { | |
if (err != null) { | |
return err | |
} | |
// console.log("creating rater", elements); | |
elements.forEach((element, i) => { | |
if (!element) return; | |
// first run | |
let ratingEl = getNewRater(); | |
element.appendChild(ratingEl); | |
const observer = new MutationObserver(debounce(mutator(watcherQs[i]))); | |
const config = { childList: true, attributes: true }; | |
observer.observe(element, config); | |
}); | |
}); | |
function getCompanyRatings(company, sp) { | |
sp ||= 'ddg' | |
const url = `http://localhost:3000/rating?company=${company}&sp=${sp}`; | |
return fetch(url).then(resp => resp.json()).then(data => { | |
if (!data.success) return Promise.reject('failed_response'); | |
console.log(data); | |
let texts = data.result.ratings.map(r => `${r.provider}: ${r.rating}`) | |
texts.push(`data_breached: ${data.result.breached}`); | |
return texts.join("\n"); | |
}); | |
} | |
// _window.addEventListener('load', fireUp); | |
_window.getRatings = getCompanyRatings; | |
})(window); |
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
#!/usr/bin/env bash | |
curl 'https://raw.githubusercontent.com/ikouchiha47/devtools/master/berater/install.sh' | bash -s -- -y |
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
const http = require('http'); | |
const url = require('url'); | |
const { GoogleResults, DDGResults } = require('./sniffratings'); | |
function handleRatingSearch(company, sp) { | |
company = company.trim(); | |
if (!company) return Promise.reject('empty_content') | |
let fetcher = { getReviews: () => Promise.reject("invalid_search_provider") } | |
if (sp == 'google') { | |
fetcher = GoogleResults.init(company) | |
} else if (sp == 'ddg') { | |
fetcher = DDGResults.init(company) | |
} | |
return fetcher.getReviews(); | |
// Refine google search or google search twice parallely for each | |
// Get rating from google search and also try to get rating from website. Combine to average. | |
// | |
// GoogleResults.init('Turing').getReviews(). | |
// then(results => results.filter(r => r.status == 'fulfilled')). | |
// then(results => Promise.allSettled(results.map((r, i) => write(`scraped-${i}.html`, r.value)))). | |
// catch(err => console.error(err)); | |
} | |
const requestListener = function(req, res) { | |
res.setHeader("Content-Type", "application/json"); | |
const parsedUrl = url.parse(req.url, true); | |
const pathname = parsedUrl.pathname; | |
const company = parsedUrl.query.company; | |
const sp = parsedUrl.query.sp || 'ddg'; | |
if (pathname == "/rating") { | |
return handleRatingSearch(company, sp).then(result => { | |
res.writeHead(200); | |
console.log(result); | |
res.end(JSON.stringify({ success: true, result })); | |
}).catch(err => { | |
res.writeHead(422); | |
res.end(`{"error": ${err}, "success": false}`) | |
}) | |
} | |
res.end(`{"error": "invalidpath"}`) | |
}; | |
const server = http.createServer(requestListener); | |
const port = process.env.PORT || 3000; | |
server.listen(port, () => { | |
console.log(`Server is running on :${port}`); | |
}); |
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
const fs = require('node:fs/promises'); | |
const GoogleURL = `https://google.com/search?q` | |
const DDGURL = `https://html.duckduckgo.com/html?q` | |
const BingURL = `https://www.bing.com/search?q` | |
const Providers = ["glassdoor.com", "ambitionbox.com"] | |
const write = (fileName, data) => { | |
log.on("writing to file " + fileName); | |
return fs.writeFile(`${fileName}`, data, { flag: 'w' }) | |
} | |
let agents = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.3", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.3", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.3", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.", | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 OPR/111.0.0.", | |
] | |
const ua = { | |
"User-Agent": agents[Math.floor(Math.random() * 10) % 6] | |
} | |
const log = { | |
on: console.log.bind(console), | |
off: () => { }, | |
} | |
const searchProviders = (company, searchprovider, url, qbuilder) => { | |
const resolver = (query, provider) => { | |
const searchURL = `${url}=${query.replaceAll(" ", "+")}`; | |
let scope = { text: '', provider: '' }; | |
log.on(searchURL); | |
return fetch(searchURL, { headers: { ...ua } }).then(resp => resp.text()).then(t => { | |
if (t.includes('captcha')) { | |
return Promise.reject(`provider(${provider}) for your search result bitched out`) | |
} | |
scope.text = t; scope.provider = provider; | |
const filename = `tmp/${searchprovider}_${company.toLowerCase()}_${provider.split('.').shift()}.html`; | |
return write(filename, t); | |
}).then(() => scope).catch(err => { log.on(err); throw err }); | |
} | |
return Promise.allSettled( | |
Providers.map(provider => resolver(qbuilder(company, provider), provider)) | |
).then(results => ( | |
results. | |
filter(result => result.status === 'fulfilled'). | |
map(result => result.value) | |
)) | |
} | |
const findRatingsInSearch = (searchResult, provider, rx) => { | |
// const results = {}; | |
while ((m = rx.exec(searchResult)) !== null) { | |
if (m.index === rx.lastIndex) { | |
regex.lastIndex++; | |
} | |
if (m && m.groups && m.groups.rating) { | |
// results[provider] = m.groups.rating | |
return m.groups.rating | |
} | |
} | |
return null; | |
} | |
const GoogleResults = { | |
_company: '', | |
_baseURL: GoogleURL, | |
_buildQ: (company, provider) => `site://${provider} ${company} reviews`, | |
init: function(company) { this._company = company; return this; }, | |
rx: /<a href=([^>]+)\/?>/gm, | |
frx: /\/url\?q=([^&]+)/, | |
rrx: /Rating[\s\S]*?(?<rating>(\d\.\d))/gm, | |
getReviews: function() { | |
return searchProviders(this._company, 'google', this._baseURL, this._buildQ).then(results => { | |
return results.map(result => ({ | |
rating: findRatingsInSearch(result.text, result.provider, this.rrx), | |
provider: result.provider, | |
})); | |
}); | |
}, | |
_aggregateReviews: function() { | |
// let searchRes = search(this._company, GoogleURL, buildQuery(this._company)); | |
// return searchRes. | |
// then(data => cleanHTML(data, GoogleResults.rx)). | |
// then(urls => findURLs(urls, this._company, GoogleResults.frx)). | |
return Promise.reject("not_implemented") | |
}, | |
} | |
const BingResults = { | |
...GoogleResults, | |
_baseURL: BingURL, | |
getReviews: function() { | |
return searchProviders(this._company, 'bing', this._baseURL, this._buildQ).then(results => { | |
return results.map(result => ({ | |
rating: '', | |
provider: result.provider, | |
})); | |
}); | |
} | |
} | |
const DDGResults = { | |
...GoogleResults, | |
_baseURL: DDGURL, | |
_buildQ: (company, provider) => `site:${provider} ${company} ratings`, | |
rrx: /<a class="result__snippet"[^>]+>[\s\S]*?(?<rating>(\d\.\d))/m, | |
getReviews: function() { | |
return searchProviders(this._company, 'ddg', this._baseURL, this._buildQ).then(results => { | |
return results.map(result => ({ | |
rating: findRatingsInSearch(result.text, result.provider, this.rrx), | |
provider: result.provider, | |
})); | |
}); | |
} | |
} | |
module.exports = { | |
GoogleResults: GoogleResults, | |
BingResults: BingResults, | |
DDGResults: DDGResults, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment