Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save beccasaurus/7f13e4d15a05bc1b1128eae1687da255 to your computer and use it in GitHub Desktop.
Save beccasaurus/7f13e4d15a05bc1b1128eae1687da255 to your computer and use it in GitHub Desktop.
πŸ”₯πŸ’‹ OkCupid "Who Likes You" Extension

πŸ’˜ OkCupid "Who Likes You" Extension


Yes, I did.

The Who Likes You page SUCKS to manage.

Initially, I just created a small JavaScript function to automatically scroll down the page, because it can take a few minutes to scroll all the way down.

Then I wanted to be able to filter.

So. Yeah.

Here you go, ladies.


Install Chrome Extension

  1. Download Extension as a zip file
  2. Extract the zip file
  3. Open the chrome://extensions page, which manages Chrome extensions (More Tools > Extensions)
  4. Enable "Developer mode" – look for this in the top-right corner: πŸ”˜ Developer mode
  5. Click "Load unpacked" and browse to folder extracted from the zip file & click "Open"
  6. Visit the Who Likes You page
  7. You should see a πŸ”₯ button (like the screenshot above)
  8. (optional) "Load All" will scroll all the way down the page & notify you when it's done
  • KEEP THE PAGE OPEN
  • The page has to actually be open and visible or else the scrolling will stop
  • Tip: I usually open a new Chrome window while the page loads
  • If you previously disallowed OkCupid's notifications, then you won't get a system-wide notification to let you know when it's done loading (a little green icon will show up on the tab instead)
  • To allow notifications from OkCupid click the little πŸ”’ in the URL bar and set Notifications to Allow
  1. Add any filter options and press <ENTER> or click the [Apply] button
  2. ViolΓ‘! You loaded all matches and filtered
  • Note: the filter functionality works, regardless of how many users are loaded so, if you want to scroll down a bit yourself, all of the users you load will work with the filter

Happy fishing, y'all 🎣

chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([
{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: {
schemes: ["https"],
hostEquals: "www.okcupid.com",
pathPrefix: "/who-likes-you"
}
})
],
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}
]);
});
});
console.log("Loading Content Script...")
let loadingStatus = "not loaded"
function loadAll(sendResponse) {
window.loadingStatus = "loading"
scrollToBottomOfWhoLikesYou(() => {
window.loadingStatus = "loaded"
// sendResponse({ message: "Complete", event: "loadAll" })
chrome.runtime.sendMessage({ event: "LOAD_DONE" })
})
}
function loadGivenNumber(count, sendResponse) {
window.loadingStatus = "loading"
scrollUntilGivenNumberOfUsers(count, () => {
window.loadingStatus = "loaded"
//sendResponse({ message: "Complete", event: "load", count: count })
chrome.runtime.sendMessage({ event: "LOAD_DONE" })
})
}
function getLoadingStatus(sendResponse) {
sendResponse({ status: loadingStatus, event: "getLoadingStatus" })
}
// So.
// Right now, this actually goes thru each row and looks at it.
// Thousands.
// So.
// Next up: local caching and optimization
function applyFilter(queryParams, sendResponse) {
showUsers((userInfo) => {
if (queryParams.minimumAge && userInfo.age < queryParams.minimumAge) return false
if (queryParams.maximumAge && userInfo.age > queryParams.maximumAge) return false
if (queryParams.minimumMatchPercentage && userInfo.matchPercentage < queryParams.minimumMatchPercentage) return false
if (queryParams.messagedMe && ! userInfo.messagedMe) return false
if (queryParams.currentlyOnline && ! userInfo.isOnline) return false
if (queryParams.allowedCities.length > 0
&& ! queryParams.allowedCities.map(s => s.toLowerCase()).includes(userInfo.city.toLowerCase()) ) return false
return true
})
chrome.runtime.sendMessage({ event: "APPLY_DONE" })
//sendResponse({ message: "Complete", event: "applyFilter" })
}
function getUserCounts(sendResponse) {
const totalCount = document.querySelectorAll(".userrow-bucket > a").length
const hiddenCount = document.querySelectorAll(".userrow-bucket > a[style='display: none;']").length
const visibleCount = totalCount - hiddenCount
sendResponse({ totalCount, hiddenCount, visibleCount })
}
const goToTop = () => window.scrollTo(0, 0)
const goToBottom = () => window.scrollTo(0,document.body.scrollHeight)
function onMessage(message, sender, sendResponse) {
console.log("onMessage", message)
switch(message.event) {
case "loadAll":
loadAll(sendResponse)
break
case "load":
loadGivenNumber(message.count, sendResponse)
break
case "getLoadingStatus":
getLoadingStatus(sendResponse)
break
case "applyFilter":
applyFilter(message.query, sendResponse)
break
case "getUserCounts":
getUserCounts(sendResponse)
break
case "goToTop":
goToTop()
break
case "goToBottom":
goToBottom()
break
default:
console.log("Unknown event:", message.event)
}
}
chrome.runtime.onMessage.addListener(onMessage)
console.log("Content Script loaded")
{
"name": "Rebecca's OkCupid Extension",
"description": "Rebecca's OkCupid Extension with features for managing Who Likes You etc",
"version": "1.0",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"page_action": {
"default_title": "Rebecca's OkCupid extensions",
"default_icon": "icon-32.png",
"default_popup": "popup.html"
},
"content_scripts":
[
{
"run_at": "document_end",
"matches": ["https://www.okcupid.com/who-likes-you*"],
"js": ["contentScript.js", "okCupidContentScript_Original.js"]
}
],
"permissions" : [
"tabs",
"activeTab",
"storage",
"declarativeContent"
],
"icons": {
"32": "icon-32.png",
"48": "icon-48.png"
},
"manifest_version": 2
}
Notification.requestPermission().then(function(status) {
if (status === "denied")
console.log("Notifications denied. Try clicking the website's little lock & see if you can allow from there")
else
console.log(`Notifications Permission: ${status}`)
})
function canSendNotification() {
return (window.Notification && Notification.permission === "granted")
}
function notifyOrAlert(message) {
if (canSendNotification())
new Notification(message)
else
alert(message)
}
function getLastProfileUrl() {
var rows = document.querySelectorAll(".userrow-bucket > a")
if (rows.length == 0)
return null
else
return rows[rows.length - 1].href
}
function getAllDisplayedProfilesAsLinks() {
return document.querySelectorAll(".userrow-bucket > a")
}
// actually, this is all profiles right not
function countOfDisplayedProfiles() {
return getAllDisplayedProfilesAsLinks().length
}
var lastProfileUrl = getLastProfileUrl();
var repeatedCount = 0
// Stop Scrolling when, after scrolling down, the last profile
// at the bottom is the same, 3 times in a row
function doneScrolling() {
var currentLastProfileUrl = getLastProfileUrl()
if (currentLastProfileUrl == lastProfileUrl) {
repeatedCount++
return (repeatedCount >= 3)
} else {
window.repeatedCount = 0
window.lastProfileUrl = currentLastProfileUrl
return false
}
}
// timeout = 500? sure, we'll try "swiping" twice a second
function scrollDown(callback) {
window.scrollBy(0,10000);
setTimeout(callback, 500)
}
function finishScrolling(done) {
// set the target of all to be _blank now that they're all loaded
// so I won't accidentally click and lose all of the loaded users
getAllDisplayedProfilesAsLinks().forEach(a => a.setAttribute("target", "_blank"))
notifyOrAlert(`Finished Scrolling (${countOfDisplayedProfiles()})`)
done()
}
function scrollToBottomOfWhoLikesYou(done) {
scrollDown(function() {
if (doneScrolling())
finishScrolling(done)
else
scrollToBottomOfWhoLikesYou(done)
})
}
function scrollUntilGivenNumberOfUsers(count, done) {
scrollDown(function() {
if (countOfDisplayedProfiles() >= count || doneScrolling())
finishScrolling(done)
else
scrollUntilGivenNumberOfUsers(count, done)
})
}
// So I can check and see how far I am thru the whole list...
function howFarAmI(nameAndAge) {
var textToSearch = nameAndAge
// get the part with the name + age
var rows = document.querySelectorAll(".userrow-bucket > a .userInfo-username")
// find all matching (incase many)
var matchingIndices = []
for (var i = 0; i < rows.length - 1; i++) {
if (rows[i].innerText == textToSearch)
matchingIndices.push(i)
}
if (matchingIndices.length == 0) {
console.log(`No people found for: ${textToSearch}`)
} else if (matchingIndices.length > 1) {
console.log(`Found ${matchingIndices.length} potential matches`)
} else {
var matchingIndex = matchingIndices[0]
console.log(`Found "${textToSearch}" @ ${matchingIndex} out of total of ${rows.length}`)
console.log(`I guess you have ${matchingIndex} more to go!`)
}
}
function userInfoFromRow(anchor) {
try {
const href = anchor.href
const username = anchor.pathname.split("/").pop()
const [name, age] = anchor.querySelector(".userInfo-username-name").innerText.split(", ")
const [city, state] = anchor.querySelector(".userInfo-meta-location").innerText.split(", ")
const matchPercentage = parseInt(anchor.querySelector(".match-info-percentage").innerText)
const isOnline = anchor.querySelector(".onlinedot") != null
const messagedMe = anchor.querySelector(".i-messages") != null
return { href, username, name, age, city, state, matchPercentage, isOnline, messagedMe }
} catch (e) {
console.log("Error parsing anchor", anchor, e)
}
}
// callback is called with (userInfo, rowAnchor)
function forEachUserRow(callback) {
getAllDisplayedProfilesAsLinks().forEach(anchor => {
var info = userInfoFromRow(anchor)
if (info != null)
callback(info, anchor)
})
}
const hideUser = (anchor) => anchor.style.display = "none"
const showUser = (anchor) => anchor.style.display = "block"
function showUsers(matcher) {
forEachUserRow((info, anchor) => {
if (matcher(info))
showUser(anchor)
else
hideUser(anchor)
})
}
function hideUsers(matcher) {
forEachUserRow((info, anchor) => {
if (matcher(info))
hideUser(anchor)
else
showUser(anchor)
})
}
function hideUnderPercentage(percentageNumber) {
hideUsers(user => user.matchPercentage < percentageNumber)
}
// city only, e.g. "Seattle"
function hideThoseNotInCity(city) {
hideUsers(user => user.city != city)
}
function hideThoseAboveAge(ageNumber) {
hideUsers(user => user.age > ageNumber)
}
fieldset { margin-top: 20px; }
input { display: block; }
input[type=checkbox] { display: inline; }
#loadGivenNumber input { display: inline; }
<!doctype html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="popup.css">
<fieldset>
<legend>❀️ Filter Likes</legend>
<form id="filterForm">
<p>Age Range</p>
<input id="minimumAge" placeholder="Minimum age, e.g. 21" size="30">
<input id="maximumAge" placeholder="Maximum age, e.g. 50" size="30">
<p>Match Percentage</p>
<input id="minimumMatchPercentage" placeholder="Minimum percentage, e.g. 90" size="30">
<p>Allowed Cities</p>
<input id="allowedCities" placeholder="e.g. Seattle, Kirkland, Issaquah" size="30">
<p>
Users who messaged me
<input id="messagedMe" type="checkbox">
</p>
<p>
Users who are online
<input id="currentlyOnline" type="checkbox">
</p>
<hr>
<input id="applyFilter" type="submit" value="Apply">
</form>
</fieldset>
<fieldset>
<legend>♻️ Getting All Likes</legend>
<p>
Total Count:
<span id="totalUserCount"></span>
</p>
<p id="filteredCount" style="display: none;">
Filtered Count:
<span id="filteredUserCount"></span>
</p>
<form id="loadGivenNumber">
<input id="load" type="submit" value="Load">
<input id="loadCount" placeholder="# to load, e.g. 500">
</form>
<input id="loadAll" type="button" value="Load All">
<hr>
<input id="goToTop" type="button" value="⬆ Go to top">
<input id="goToBottom" type="button" value="⬇ Go to bottom">
</fieldset>
<script src="popup.js"></script>
function sendMessage(message, responseCallback) {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, message, responseCallback);
});
}
function getAndDisplayUserCounts() {
sendMessage({ event: "getUserCounts" }, function(response) {
console.log("User Counts Response from the Content Script:", response)
document.getElementById("totalUserCount").innerText = response.totalCount
if (response.visibleCount == response.totalCount) {
document.getElementById("filteredCount").style.display = "none"
} else {
document.getElementById("filteredCount").style.display = "block"
document.getElementById("filteredUserCount").innerText = response.visibleCount
}
})
}
function applyFilter() {
const minimumAge = parseInt(document.getElementById("minimumAge").value)
const maximumAge = parseInt(document.getElementById("maximumAge").value)
const minimumMatchPercentage = parseInt(document.getElementById("minimumMatchPercentage").value)
const allowedCities = document.getElementById("allowedCities").value.split(",").map(s => s.trim()).filter(Boolean)
const messagedMe = document.getElementById("messagedMe").checked
const currentlyOnline = document.getElementById("currentlyOnline").checked
const queryObject = { minimumAge, maximumAge, minimumMatchPercentage, allowedCities, messagedMe, currentlyOnline }
// const filterButton = document.getElementById("applyFilter")
// filterButton.setAttribute("disabled", "disabled")
// filterButton.value = "Filtering..."
sendMessage({ event: "applyFilter", query: queryObject })
}
function applyFilterComplete() {
getAndDisplayUserCounts()
filterButton.removeAttribute("disabled")
filterButton.value = "Apply"
}
let updateCountWhileLoadingInterval;
function enableLoadingUI() {
if (! updateCountWhileLoadingInterval) {
const loadButton = document.getElementById("loadAll")
loadButton.setAttribute("disabled", "disabled")
loadButton.value = "Loading... (this may take a couple minutes)"
window.updateLoadedCountInterval = setInterval(getAndDisplayUserCounts, 500)
}
}
function disableLoadingUI() {
clearInterval(updateCountWhileLoadingInterval)
const loadButton = document.getElementById("loadAll")
loadButton.removeAttribute("disabled")
loadButton.value = "Load All"
}
function loadAll() {
enableLoadingUI()
sendMessage({ event: "loadAll" })
}
function loadComplete() {
disableLoadingUI()
}
function loadGivenNumber() {
const countInput = document.getElementById("loadCount")
const countToLoad = parseInt(countInput.value)
countInput.value = ""
if (countToLoad < 1) return;
enableLoadingUI()
sendMessage({ event: "load", count: countToLoad })
}
function checkIfLoadingInProgress() {
sendMessage({ event: "getLoadingStatus" }, (status) => {
if (status == "loading") {
enableLoadingUI()
setTimeout(checkIfLoadingInProgress, 1000)
} else {
disableLoadingUI()
}
})
}
document.getElementById("loadGivenNumber").addEventListener("submit", (e) => { e.preventDefault(); loadGivenNumber() })
document.getElementById("loadAll").addEventListener("click", loadAll)
document.getElementById("filterForm").addEventListener("submit", (e) => { e.preventDefault(); applyFilter() })
document.getElementById("goToTop").addEventListener("click", () => sendMessage({ event: "goToTop" }))
document.getElementById("goToBottom").addEventListener("click", () => sendMessage({ event: "goToBottom" }))
window.onload = function() {
getAndDisplayUserCounts()
checkIfLoadingInProgress()
}
function onMessage(message, sender, sendResponse) {
console.log("onMessage", message)
switch(message.event) {
case "APPLY_DONE":
applyFilterComplete()
break
case "LOAD_DONE":
loadComplete()
break
default:
console.log("Unknown event:", message.event)
}
}
chrome.runtime.onMessage.addListener(onMessage)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment