Wordle Tools

Some enhancements of the Wordle game.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Wordle Tools
// @namespace    CCCC_David
// @version      0.2.0
// @description  Some enhancements of the Wordle game.
// @author       CCCC_David
// @match        https://www.powerlanguage.co.uk/wordle/
// @match        https://www.nytimes.com/games/wordle/index.html
// @grant        none
// @license      MIT
// ==/UserScript==

(async () => {
    'use strict';

    const SUCCESS_MSG_TIMEOUT = 3000;

    const allowedPolicy = window.trustedTypes?.createPolicy?.('allowedPolicy', {createHTML: (x) => x});
    const createTrustedHTML = (html) => (allowedPolicy ? allowedPolicy.createHTML(html) : html);

    let puzzleList = null;

    const getPuzzleList = async () => {
        if (puzzleList) {
            return puzzleList;
        }
        try {
            for (const el of document.getElementsByTagName('script')) {
                const scriptSrc = el.src;
                if (/\bmain\.\w+\.js$/u.test(scriptSrc ?? '')) {
                    // eslint-disable-next-line no-await-in-loop
                    const res = await fetch(scriptSrc, {
                        method: 'GET',
                        mode: 'same-origin',
                        redirect: 'follow',
                    });
                    // eslint-disable-next-line no-await-in-loop
                    const jsCode = await res.text();
                    puzzleList = JSON.parse(jsCode.match(/\["cigar".*?"shave"\]/u)[0]);
                    return puzzleList;
                }
            }
        } catch (e) {
            // eslint-disable-next-line no-console
            console.error(e);
        }
        return null;
    };

    const appendPuzzleIdToTitle = (gameAppElement, puzzleId) => {
        const puzzleIdInTitleSpan = document.createElement('span');
        puzzleIdInTitleSpan.id = 'wordle-tools-puzzle-id-in-title';
        puzzleIdInTitleSpan.innerText = puzzleId;
        gameAppElement.$game.parentElement.querySelector('div[class="title"]').appendChild(puzzleIdInTitleSpan);
    };

    const clearGameState = (gameAppElement) => {
        localStorage.removeItem('gameState');
        localStorage.removeItem('nyt-wordle-state');
        gameAppElement.gameStatus = 'IN_PROGRESS';
        gameAppElement.canInput = true;
        gameAppElement.boardState = new Array(6).fill('');
        gameAppElement.evaluations = new Array(6).fill(null);
        gameAppElement.letterEvaluations = {};
        gameAppElement.rowIndex = 0;
        gameAppElement.tileIndex = 0;
        gameAppElement.restoringFromLocalStorage = false;
        for (const row of gameAppElement.$game.getElementsByTagName('game-row')) {
            row.removeAttribute('letters');
            for (const tile of row.shadowRoot.querySelectorAll('game-tile')) {
                tile.removeAttribute('letter');
                tile.removeAttribute('evaluation');
                tile.removeAttribute('reveal');
            }
        }
        for (const button of gameAppElement.$keyboard.shadowRoot.querySelectorAll('button')) {
            button.removeAttribute('data-state');
            button.classList.remove('fade');
        }
        gameAppElement.$game.querySelector('#game-toaster').innerHTML = '';
    };

    const clearStatistics = () => {
        localStorage.removeItem('statistics');
        localStorage.removeItem('nyt-wordle-statistics');
    };

    const jumpToPuzzleId = (gameAppElement, settingsShadowRoot, puzzleId) => {
        clearGameState(gameAppElement);
        gameAppElement.dayOffset = puzzleId;
        gameAppElement.solution = puzzleList[puzzleId % puzzleList.length];
        gameAppElement.$game.parentElement.querySelector('#wordle-tools-puzzle-id-in-title').innerText = puzzleId;
        settingsShadowRoot.getElementById('puzzle-number').innerText = `#${puzzleId}`;
    };

    const handleGameAppElement = async (gameAppElement) => {
        if (!gameAppElement) {
            return;
        }

        window.gameApp = gameAppElement;
        await getPuzzleList();

        const appendSettingsItems = (gameSettingsElement) => {
            if (!gameSettingsElement) {
                return;
            }

            const settingsShadowRoot = gameSettingsElement.shadowRoot;
            const settingsSection = settingsShadowRoot.querySelector('div[class="sections"] > section');

            const showSuccessMsg = (message, insertBeforeNode, enableElements) => {
                const successMsgElement = document.createElement('span');
                successMsgElement.innerText = message;
                successMsgElement.style.color = 'var(--color-correct)';
                insertBeforeNode.parentElement.insertBefore(successMsgElement, insertBeforeNode);
                setTimeout(() => {
                    successMsgElement.remove();
                    for (const el of enableElements) {
                        el.disabled = false;
                    }
                }, SUCCESS_MSG_TIMEOUT);
            };

            const jumpItem = document.createElement('div');
            settingsSection.appendChild(jumpItem);
            jumpItem.outerHTML = createTrustedHTML(`
                <div class="setting">
                    <div class="text">
                        <div class="title">Jump to Puzzle #</div>
                    </div>
                    <div class="control">
                        <input type="text" id="wordle-tools-jump-target-id" style="width: 3em;">
                        <button id="wordle-tools-jump-to-puzzle">Jump</button>
                    </div>
                </div>
            `);
            const jumpButton = settingsShadowRoot.getElementById('wordle-tools-jump-to-puzzle');
            const jumpInput = settingsShadowRoot.getElementById('wordle-tools-jump-target-id');
            jumpInput.value = gameAppElement.dayOffset;
            jumpButton.addEventListener('click', (e) => {
                const button = e.target;
                const inputBox = button.parentElement.querySelector('input');
                const puzzleId = parseInt(inputBox.value, 10);
                if (Number.isNaN(puzzleId) || puzzleId < 0) {
                    inputBox.value = gameAppElement.dayOffset;
                    return;
                }
                button.disabled = true;
                inputBox.disabled = true;
                inputBox.value = puzzleId;
                jumpToPuzzleId(gameAppElement, settingsShadowRoot, puzzleId);
                showSuccessMsg('Jumped to ', inputBox, [button, inputBox]);
            });
            jumpInput.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    jumpButton.click();
                }
            });

            const clearStateItem = document.createElement('div');
            settingsSection.appendChild(clearStateItem);
            clearStateItem.outerHTML = createTrustedHTML(`
                <div class="setting">
                    <div class="text">
                        <div class="title">Clear Current Game State</div>
                    </div>
                    <div class="control">
                        <button id="wordle-tools-clear-game-state">Clear</button>
                    </div>
                </div>
            `);
            settingsShadowRoot.getElementById('wordle-tools-clear-game-state').addEventListener('click', (e) => {
                const button = e.target;
                button.disabled = true;
                clearGameState(gameAppElement);
                showSuccessMsg('Cleared ', button, [button]);
            });

            const clearStatsItem = document.createElement('div');
            settingsSection.appendChild(clearStatsItem);
            clearStatsItem.outerHTML = createTrustedHTML(`
                <div class="setting">
                    <div class="text">
                        <div class="title">Clear Game Statistics</div>
                    </div>
                    <div class="control">
                        <button id="wordle-tools-clear-statistics">Clear</button>
                    </div>
                </div>
            `);
            settingsShadowRoot.getElementById('wordle-tools-clear-statistics').addEventListener('click', (e) => {
                const button = e.target;
                button.disabled = true;
                clearStatistics();
                showSuccessMsg('Cleared ', button, [button]);
            });

            const feedbackSection = settingsShadowRoot.querySelectorAll('div[class="sections"] > section')[1];
            const wordleToolsMarker = document.createElement('div');
            feedbackSection.appendChild(wordleToolsMarker);
            wordleToolsMarker.outerHTML = createTrustedHTML(`
                <div class="setting">
                    <div class="text">
                        <div class="title">Wordle Tools v0.2.0 Enabled</div>
                    </div>
                </div>
            `);
        };

        const gameSettingsObserver = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    for (const el of mutation.addedNodes) {
                        if (el.nodeName.toLowerCase() === 'game-settings') {
                            appendSettingsItems(el);
                        }
                    }
                }
            }
        });

        gameSettingsObserver.observe(gameAppElement.$game, {
            subtree: true,
            childList: true,
        });

        appendSettingsItems(gameAppElement.$game.getElementsByTagName('game-settings')[0]);
        appendPuzzleIdToTitle(gameAppElement, gameAppElement.dayOffset);
    };

    const gameAppObserver = new MutationObserver(async (mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList') {
                for (const el of mutation.addedNodes) {
                    if (el.nodeName.toLowerCase() === 'game-app') {
                        // eslint-disable-next-line no-await-in-loop
                        await handleGameAppElement(el);
                    }
                }
            }
        }
    });

    gameAppObserver.observe(document.documentElement, {
        subtree: true,
        childList: true,
    });

    await handleGameAppElement(document.getElementsByTagName('game-app')[0]);
})();