Instantly share code, notes, and snippets.
Last active
May 1, 2024 01:08
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save q00u/f7f61ac36ea08fea8b27f87cbd3050f7 to your computer and use it in GitHub Desktop.
This file contains 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 Share Wordle Guesses | |
// @namespace https://gist.github.com/q00u | |
// @version 0.9 | |
// @downloadURL https://gist.github.com/q00u/f7f61ac36ea08fea8b27f87cbd3050f7/raw/ShareWordleGuesses.user.js | |
// @updateURL https://gist.github.com/q00u/f7f61ac36ea08fea8b27f87cbd3050f7/raw/ShareWordleGuesses.user.js | |
// @supportURL https://gist.github.com/q00u/f7f61ac36ea08fea8b27f87cbd3050f7 | |
// @description Copy Wordle guesses (but not _answer_) for Slack comment | |
// @author Phoenix G | |
// @match https://www.nytimes.com/games/wordle/index.html | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=nytimes.com | |
// @grant unsafeWindow | |
// ==/UserScript== | |
(async function() { | |
'use strict'; | |
// Your code here... | |
const grabGuesses = (wordleState) => { | |
console.log('wordleState:', wordleState); | |
// Build output strings | |
const output = []; | |
let finalOut = ''; | |
let done = false; | |
// Dangit, evaluation is no longer stored in localStorage | |
const board = document.querySelectorAll("[class*=Board-module_board_]")[0]; | |
for (let i=0; i<6 && !done; i++) { | |
let thisLine = ''; // The potential output | |
let altLine = ''; // Alternative line for *nearly* correct guesses | |
let correctCount = 0; | |
//let word = wordleState.game.boardState[i]; // The line as-is | |
let word = wordleState[i]; // The line as-is | |
const row = board.childNodes[i]; | |
let allCorrect = true; | |
// For each letter... | |
for (let j=0; j<5; j++) { | |
const c = word[j]; | |
const evaluation = row.childNodes[j].firstChild.dataset.state; | |
switch(evaluation) { | |
case 'absent': | |
case 'present': | |
allCorrect = false; | |
thisLine += `${c}`; | |
altLine += `${c}`; | |
break; | |
case 'correct': | |
thisLine += `${c.toUpperCase()}`; | |
altLine += '-'; | |
correctCount += 1; | |
break; | |
case 'tbd': break; //Correct line, while it is animating. If we see this, we're done | |
default: throw 'Unexpected row evaluation: ' + evaluation; | |
} | |
} | |
// const evaluation = wordleState.evaluations[i]; // The evaluation for this word | |
// // console.log('Evaluating line:', word, evaluation); | |
// let allCorrect = true; | |
// // For each letter... | |
// for (let j=0; j<5; j++) { | |
// const c = word[j]; | |
// switch(evaluation[j]) { | |
// case 'absent': | |
// case 'present': // Slack can't apply italics inside code lines nor around letters that touch | |
// allCorrect = false; | |
// thisLine += `${c}`; | |
// break; | |
// case 'correct': thisLine += `${c.toUpperCase()}`; break; | |
// default: throw 'Unexpected row evaluation : ' + evaluation[j]; | |
// } | |
// } | |
// Wait, was this the answer? | |
if (allCorrect) { | |
// We're done, pretty-print results | |
//let finalOut = ''; | |
for (let k=0; k<output.length; k++) { | |
finalOut += `\`${output[k]}\`\n`; | |
} | |
console.log(finalOut); | |
done = true; | |
} else { | |
// Otherwise, add to output and continue | |
if (correctCount < 4) { | |
output.push(thisLine); | |
} else { | |
output.push(altLine); | |
} | |
} | |
} | |
// Add snackbar for copy button to use later | |
const snackbarDiv = document.createElement('div'); | |
snackbarDiv.id = 'snackbar'; | |
snackbarDiv.innerText = 'Guesses copied!'; | |
document.body.append(snackbarDiv); | |
// Style it | |
function GM_addStyle(css) { | |
const style = document.getElementById("GM_addStyleBy8626") || (function() { | |
const style = document.createElement('style'); | |
style.type = 'text/css'; | |
style.id = "GM_addStyleBy8626"; | |
document.head.appendChild(style); | |
return style; | |
})(); | |
const sheet = style.sheet; | |
sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length); | |
} | |
// Position snackbar at the bottom and in the middle of the screen | |
GM_addStyle('#snackbar{background-color:var(--modal-content-bg);border:1px solid var(--color-tone-6);border-radius:2px;box-shadow:0 4px 23px 0 rgba(0,0,0,.2);color:var(--color-tone-1);height:18px;margin-left:-60px;min-width:120px;padding:16px;position:fixed;right:24px;text-align:center;top:64px;visibility:hidden;z-index:var(--modal-z-index)}'); | |
// Show the snackbar when clicking a button | |
GM_addStyle('#snackbar.show{animation:.5s fadein,.5s 2.5s fadeout;visibility:visible;webkit-animation:fadein 0.5s,fadeout 0.5s 2.5s}'); | |
// Animations to fade the snackbar in and out | |
GM_addStyle('@-webkit-keyframes fadein {from {bottom: 0;opacity: 0;}to {bottom: 30px;opacity: 1;}}'); | |
GM_addStyle('@keyframes fadein {from {bottom: 0;opacity: 0;}to {bottom: 30px;opacity: 1;}}'); | |
GM_addStyle('@-webkit-keyframes fadeout {from {bottom: 30px;opacity: 1;}to {bottom: 0;opacity: 0;}}'); | |
GM_addStyle('@keyframes fadeout {from {bottom: 30px;opacity: 1;}to {bottom: 0;opacity: 0;}}'); | |
// Build SVG | |
const xmlns = 'http://www.w3.org/2000/svg'; | |
const svg = document.createElementNS(xmlns, 'svg'); | |
svg.setAttribute('width', '24px'); | |
svg.setAttribute('height', '24px'); | |
svg.setAttribute('viewBox', '0 0 24 24'); | |
svg.setAttribute('class', 'game-icon'); | |
svg.setAttribute('data-testid', 'icon-copy'); | |
svg.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'); | |
const rect1 = document.createElementNS(xmlns, 'rect'); | |
rect1.setAttribute('x', '2'); | |
rect1.setAttribute('y', '1'); | |
rect1.setAttribute('rx', '1'); | |
rect1.setAttribute('ry', '1'); | |
rect1.setAttribute('width', '16'); | |
rect1.setAttribute('height', '20'); | |
rect1.setAttribute('style', 'fill: none; stroke:var(--color-tone-1); strike-width: 1'); | |
const rect2 = document.createElementNS(xmlns, 'rect'); | |
rect2.setAttribute('x', '5'); | |
rect2.setAttribute('y', '5'); | |
rect2.setAttribute('rx', '0.5'); | |
rect2.setAttribute('ry', '0.5'); | |
rect2.setAttribute('width', '10'); | |
rect2.setAttribute('height', '1'); | |
rect2.setAttribute('style', 'fill:var(--color-tone-1)'); | |
const rect3 = document.createElementNS(xmlns, 'rect'); | |
rect3.setAttribute('x', '5'); | |
rect3.setAttribute('y', '8'); | |
rect3.setAttribute('rx', '0.5'); | |
rect3.setAttribute('ry', '0.5'); | |
rect3.setAttribute('width', '10'); | |
rect3.setAttribute('height', '1'); | |
rect3.setAttribute('style', 'fill:var(--color-tone-1)'); | |
const rect4 = document.createElementNS(xmlns, 'rect'); | |
rect4.setAttribute('x', '5'); | |
rect4.setAttribute('y', '11'); | |
rect4.setAttribute('rx', '0.5'); | |
rect4.setAttribute('ry', '0.5'); | |
rect4.setAttribute('width', '10'); | |
rect4.setAttribute('height', '1'); | |
rect4.setAttribute('style', 'fill:var(--color-tone-1)'); | |
const rect5 = document.createElementNS(xmlns, 'rect'); | |
rect5.setAttribute('x', '5'); | |
rect5.setAttribute('y', '14'); | |
rect5.setAttribute('rx', '0.5'); | |
rect5.setAttribute('ry', '0.5'); | |
rect5.setAttribute('width', '6'); | |
rect5.setAttribute('height', '1'); | |
rect5.setAttribute('style', 'fill:var(--color-tone-1)'); | |
const path1 = document.createElementNS(xmlns, 'path'); | |
path1.setAttribute('d', 'M18,3h3a1,1 0 0 1 1,1v18a-1,1 0 0 1 -1,1h-14a-1,-1 0 0 1 -1,-1v-1'); | |
path1.setAttribute('style', 'fill: none; stroke: var(--color-tone-1); stroke-width: 1'); | |
svg.appendChild(rect1); | |
svg.appendChild(rect2); | |
svg.appendChild(rect3); | |
svg.appendChild(rect4); | |
svg.appendChild(rect5); | |
svg.appendChild(path1); | |
//console.log(svg); | |
let btn = document.createElement('button'); | |
btn.appendChild(svg); | |
//btn.onclick = copyToClipBoard(finalOut); | |
btn.addEventListener('click', function() { | |
const el = document.createElement('textarea'); | |
el.value = finalOut; | |
document.body.appendChild(el); | |
el.select(); | |
document.execCommand('copy'); | |
document.body.removeChild(el); | |
//console.log('Copied to clipboard:', el.value); | |
// Now show the snackbar | |
snackbarDiv.className = 'show'; | |
setTimeout(function(){ | |
snackbarDiv.className = snackbarDiv.className.replace('show',''); | |
}, 3000); | |
}); | |
btn.type = 'button'; | |
btn.id = 'copy-button'; | |
//btn.class = 'AppHeader-module_icon__x7b46'; | |
btn.ariaLabel = 'Copy Guesses'; | |
btn.tabIndex = -1; | |
// Find the header | |
let appHeader = document.getElementsByClassName('wordle-app-header')[0]; | |
let menuRight; | |
if (appHeader === undefined) { | |
// Header tag is missing its class, probably in old style | |
console.log('Header tag has no class, trying alternate route...'); | |
appHeader = document.querySelector('game-app').shadowRoot.querySelector('game-theme-manager').querySelector('header'); | |
menuRight = appHeader.children[2]; | |
btn.className = 'icon'; | |
} else { | |
// Last child should be menuRight | |
menuRight = appHeader.lastChild; | |
// Grab class from first child of menuRight | |
const btnClass = menuRight.firstChild.className; | |
// Set class of button | |
btn.className = btnClass; | |
} | |
// Add new button to start of menuRight | |
menuRight.prepend(btn); | |
} | |
const waitForGame = (rightKey) => { | |
// WAIT FOR PUZZLE TO BE SOLVED | |
console.log('Waiting for game to resolve.'); | |
// Grab board state from local storage | |
//const wordleState = JSON.parse(unsafeWindow.localStorage['nyt-wordle-state']); | |
// Updated 2022/10/18: | |
//const wordleState = JSON.parse(unsafeWindow.localStorage['nyt-wordle-moogle/170771851']); | |
//console.log('wordleState:', typeof wordleState, wordleState); | |
const repeatcheck = () => { | |
//const wordleState = JSON.parse(unsafeWindow.localStorage['nyt-wordle-state']); | |
//console.log('rightKey in waitForGame:', rightKey); | |
const wordleState = JSON.parse(unsafeWindow.localStorage[rightKey]); | |
//const wordleState = {game: {status: 'FAIL'}}; | |
//if (wordleState.gameStatus != 'IN_PROGRESS') { | |
//if (wordleState.game.status === 'FAIL' || wordleState.game.status === 'WIN') { | |
if (wordleState.states[0].data.status === 'FAIL' || wordleState.states[0].data.status === 'WIN') { | |
console.log('Game has ended!'); | |
clearInterval(clearcheck); | |
//grabGuesses(wordleState); | |
grabGuesses(wordleState.states[0].data.boardState); | |
} | |
} | |
const clearcheck = setInterval(repeatcheck, 500); | |
} | |
const waitForWelcome = (rightKey) => { | |
// WAIT FOR WELCOME SCREEN TO CLOSE | |
console.log('Waiting for Welcome screen to close...'); | |
const repeatcheck = () => { | |
const welcome = document.querySelectorAll("[class*=Welcome-module_contentWelcome]"); | |
if (welcome.length === 0) { | |
console.log('Welcome!'); | |
clearInterval(clearcheck); | |
waitForGame(rightKey); | |
} | |
}; | |
const clearcheck = setInterval(repeatcheck, 500); | |
} | |
const waitForLocalStorage = () => { | |
// Find right localstorage | |
console.log('Waiting for localStorage to resolve...'); | |
const repeatcheck = () => { | |
const lstorage = unsafeWindow.localStorage; | |
const keys = Object.keys(lstorage); | |
//console.log(keys.length); | |
if (keys.length > 0) { | |
console.log('localStorage ready!'); | |
// Look for most recent timestamp | |
let latest = 0; | |
let rightKey; | |
keys.forEach((key) => { | |
// if (key.startsWith('nyt-wordle-moogle')) { | |
if (key.startsWith('games-state-wordleV2')) { | |
const here = JSON.parse(lstorage[key]); | |
// if (here.timestamp > latest) { | |
if (here.states[0].timestamp > latest) { | |
latest = here.states[0].timestamp; | |
rightKey = key; | |
} | |
} | |
}); | |
console.log('Checking localStorage:', rightKey); | |
clearInterval(clearcheck); | |
waitForWelcome(rightKey); | |
} | |
}; | |
const clearcheck = setInterval(repeatcheck, 500); | |
} | |
//Begin | |
waitForLocalStorage(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
0.1: Initial version
0.2: Big Change
0.3: Big Change
0.4:
0.5:
0.6:
0.7:
0.8:
0.9: