Wordle in One Chrome Plugin
/ 3 min read
Table of Contents
How?
TLDR
- User visits the wordle page
- We monitor the network requests because it contains the solution
- Submit the solution
Longer Version
When a user navigates to https://www.nytimes.com/games/wordle/index.html, a network request is made to https://www.nytimes.com/svc/wordle/v2/2024-01-28.json to get today’s solution. The response looks like this:
{ "id": 1351, "solution": "ember", "print_date": "2024-01-28", "days_since_launch": 953, "editor": "Tracy Bennett"}wordle solution
So now that we know that the page will make a request to get the solution, we need to intercept that request. You can use chrome.webRequest .onCompleted to look at all the competed requests until you see the one you want then do something with that info. Once you find the request you’re interested in, you’ll have to re-make the same request because the onCompleted doesn’t give you the response for some reason. chrome.webRequest can only run in a background-script, and we want to modify the content of the page to show the solution which can only happen in a content-script. We have to send the solution that we have now from the background-script to the content-script and we can use chrome.tabs.sendMessage to do that. The code for the background-script looks like this:
let solution = null;chrome.webRequest.onCompleted.addListener( async function (details) { if (solution != null) { return; } const url = details.url;
// make a call to url and get the response back const response = await fetch(details.url); const json = await response.json();
solution = json.solution;
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { chrome.tabs.sendMessage(tabs[0].id, { solution: solution }); });
return {}; }, { urls: ["https://www.nytimes.com/svc/wordle/v2/*"] },);background.js
On the content-script side, we need a message listener to listen to the solution being sent from the background-script.
let solution = null;chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if (request) { solution = request.solution; }});Once we have the solution in the code, I wanted the chrome plugin to click all the wordle buttons for each letter in the solution so all the user has to do is click enter:
function solve() { for (var i = 0; i < solution.length; i++) { let letterButton = document.querySelector( 'button[data-key="' + solution.charAt(i) + '"]', ); letterButton.click(); }}However, as I was testing this, I realized that the nytimes has different flows for different scenarios, and sometimes the game is not visible yet so calling solve() just throws an error because it can’t find the elements it wants to find.
I asked chatgpt how to fix that and it suggested using a MutationObserver which listens to DOM changes and invokes a callback when that happens.
// Function to check if the class name matches your criteriafunction isMatchingClassName(className) { return className.startsWith("MomentSystem");}
// Callback function to execute when mutations are observedvar callback = function (mutationsList, observer) { for (var mutation of mutationsList) { if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { if ( node.nodeType === Node.ELEMENT_NODE && Array.from(node.classList).some(isMatchingClassName) ) { solve(); } }); } }};
// Create an observer instance linked to the callback functionvar observer = new MutationObserver(callback);
// Options for the observer (what mutations to observe)var config = { attributes: false, childList: true, subtree: true };
// Start observing the document body for configured mutationsobserver.observe(document.body, config);So now we call solve() whenever we see a DOM element with a class name that starts with MomentSystem and this seems to work!
Where?
The plugin is available on the chrome web store here.
In the meantime, you can take a look at the source code on github.