Wordle in One Chrome Plugin

0:00
/0:05

demo

How?

TLDR

  1. User visits the wordle page
  2. We monitor the network requests because it contains the solution
  3. 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 criteria
function isMatchingClassName(className) {
  return className.startsWith("MomentSystem");
}

// Callback function to execute when mutations are observed
var 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 function
var 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 mutations
observer.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.