Puzsq Hotkeys

Use A and D to switch puzzles, S to toggle ANSWER RECORD, F to toggle AUTO SKIP.

// ==UserScript==
// @name         Puzsq Hotkeys
// @version      25.2.27.1
// @description  Use A and D to switch puzzles, S to toggle ANSWER RECORD, F to toggle AUTO SKIP.
// @author       Leaving Leaves
// @match        https://puzsq.logicpuzzle.app/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=logicpuzzle.app
// @grant        none
// @license      GPL
// @namespace https://greasyfork.org/users/1192854
// ==/UserScript==

'use strict';

const buttonName = "MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedSuccess MuiButton-sizeMedium MuiButton-containedSizeMedium css-m6iq7a";
let AutoSkip = false;
let reloadTimer;
let AutoNextFlag = false;
let AutoNextTimer;
const AutoNextTime = 5 * 60 * 1000;
function getIframe() {
    let iframe;
    iframe = document.getElementById("iframe1");
    if (iframe !== null) { return iframe; }
    iframe = document.getElementById("iframe2");
    if (iframe !== null) { return iframe; }
    return null;
}
function PrevPuzzle() {
    let button = document.evaluate('//*[text()="Previous Puzzle" or text()="前のパズル"]/following-sibling::*[1]/*[1]/*[1]/*[1]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (button === null) {
        console.log("No prev puzzle!");
    } else {
        console.log("Switching to prev puzzle...");
        button.firstChild.children[1].click();
    }
    clearTimeout(reloadTimer);
}
function NextPuzzle() {
    let button = document.evaluate('//*[text()="Next Puzzle" or text()="次のパズル"]/following-sibling::*[1]/*[1]/*[1]/*[1]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (button === null) {
        console.log("No next puzzle!");
    } else {
        console.log("Switching to next puzzle...");
        button.firstChild.children[1].click();
    }
    clearTimeout(reloadTimer);
}
function CheckPuzzle(t = false) {
    let button = document.evaluate('//*[text()="Answer Record" or text()="解答記録"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    let button2 = document.evaluate('//*[text()="Answer Record" or text()="解答記録" or text()="Cancel Answer" or text()="解答取消"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (t) {
        if (button !== null) { button.click(); }
        return;
    }
    if (button2 !== null) { button2.click(); }
}
function ToggleAutoskip() {
    AutoSkip ^= 1;
    if (document.querySelector("#AutoSkip") !== null) {
        document.querySelector("#AutoSkip").innerHTML = (AutoSkip ? "☑ Auto Skip" : "☐ Auto Skip");
    }
    console.log(AutoSkip ? "AutoSkip turned on." : "AutoSkip turned off.");
}
window.addEventListener("keydown", (event) => {
    if (!/puzsq\.logicpuzzle\.app\/puzzle\/\d+$/.test(window.location.href)) { return; }
    if (event.defaultPrevented) { return; }
    switch (event.key) {
        case "A":
        case "a":
            PrevPuzzle();
            break;
        case "D":
        case "d":
            NextPuzzle();
            break;
        case "S":
        case "s":
            CheckPuzzle();
            break;
        case "F":
        case "f":
            ToggleAutoskip();
            break;
        case "Q":
        case "q":
            getIframe().contentWindow.postMessage("assist", "*");
            break;
        case "W":
        case "w":
            getIframe().contentWindow.postMessage("assiststep", "*");
            break;
        case "Z":
        case "z":
            getIframe().contentWindow.postMessage("undo", "*");
            break;
        case "X":
        case "x":
            getIframe().contentWindow.postMessage("redo", "*");
            break;
        default:
            return;
    }
    event.preventDefault();
}, true,);
window.addEventListener("message", (event) => {
    // console.log(event);
    // if (!["https://puzz.link", "https://pzprxs.vercel.app"].includes(event.origin)) { return; }
    if (event.data === "Solved") {
        console.log("Puzzle solved.");
        CheckPuzzle(true);
    }
    if (event.data === "Solved" && AutoNextFlag || event.data === "Not Solved" && AutoSkip) {
        NextPuzzle();
    }
    if (event.data === "Ready to Assist") {
        getIframe().contentWindow.postMessage("assist", "*");
        AutoNextFlag = true;
        clearTimeout(AutoNextTimer);
        AutoNextTimer = setTimeout(() => {
            console.log("AutoNext timeout after " + AutoNextTime + " ms.");
            AutoNextFlag = false;
        }, AutoNextTime);
    }
}, false,);
let lastUrl = '';
setInterval(function () {
    if (getIframe() === null) { return; }
    addAutoskipButton();
    let Url = getIframe().src;
    if (AutoSkip && Url !== lastUrl) {
        clearTimeout(reloadTimer);
        if (!/puzz\.link/.test(Url)) {
            console.log("Skip non-puzz.link puzzle...");
            NextPuzzle();
        } else {
            reloadTimer = setInterval(() => {
                if (getIframe().src === null || !AutoSkip) {
                    clearInterval(reloadTimer);
                    return;
                }
                console.log("Puzz.link Assistant not found. Reload iframe.");
                getIframe().src += '';
            }, 5000);
        }
    }
    lastUrl = (AutoSkip ? Url : "");
}, 1000);

function addAutoskipButton() {
    const AutoskipButton = `<button type="button" id="AutoSkip" style="\
    display: inline-flex;\
    -webkit-box-align: center;\
    align-items: center;\
    -webkit-box-pack: center;\
    justify-content: center;\
    position: relative;\
    box-sizing: border-box;\
    -webkit-tap-highlight-color: transparent;\
    outline: 0px;\
    border: 0px currentcolor;\
    margin: 8px 8px 0px 0px;\
    cursor: pointer;\
    user-select: none;\
    vertical-align: middle;\
    appearance: none;\
    text-decoration: none;\
    font-family: Roboto, Helvetica, Arial, sans-serif;\
    font-weight: 500;\
    font-size: 0.875rem;\
    line-height: 1.75;\
    letter-spacing: 0.02857em;\
    text-transform: uppercase;\
    min-width: 64px;\
    padding: 6px 16px;\
    border-radius: 4px;\
    transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\
    color: rgb(255, 255, 255);\
    background-color: rgb(140, 72, 176);\
    box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;\
">${AutoSkip ? "☑" : "☐"} Auto Skip</button>`;
    let button = document.evaluate('//*[text()="Copy Information" or text()="情報をコピー"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (button === null) { return; }
    if (document.getElementById("AutoSkip") !== null) { return; }
    button.insertAdjacentHTML('afterend', AutoskipButton);
    document.querySelector("#AutoSkip").addEventListener("click", ToggleAutoskip, false);
}