Skip to content

Instantly share code, notes, and snippets.

@ikouchiha47
Last active July 10, 2024 09:41
Show Gist options
  • Save ikouchiha47/720d45eccfa61ef41d5262b26fca54dc to your computer and use it in GitHub Desktop.
Save ikouchiha47/720d45eccfa61ef41d5262b26fca54dc to your computer and use it in GitHub Desktop.
Suck'em ratings
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()
// ==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);
#!/usr/bin/env bash
curl 'https://raw.githubusercontent.com/ikouchiha47/devtools/master/berater/install.sh' | bash -s -- -y
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}`);
});
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