Chess.com Stockfish Bot

Simplifies move logic to reliably play the first move as White without user interaction.

Pada tanggal 17 Juli 2025. Lihat %(latest_version_link).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Chess.com Stockfish Bot
// @namespace    BottleOrg Scripts
// @version      1.7.5
// @description  Simplifies move logic to reliably play the first move as White without user interaction.
// @author       [REDACTED] - Rightful Owner([email protected], Gemini 2.5 Pro, Kimi K2
// @license      Chess.com Bot/Cheat by BottleOrg([email protected])
// @match        https://www.chess.com/play/*
// @match        https://www.chess.com/game/*
// @match        https://www.chess.com/puzzles/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @grant        GM_registerMenuCommand
// @require      https://greasyfork.org/scripts/445697/code/index.js
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @run-at       document-start
// ==/UserScript==

const currentVersion = '1.7.5';
const scriptURL = 'https://greasyfork.org/en/scripts/526240-chess-com-stockfish-bot';

function checkForUpdate() {
    console.log("Checking for script updates...");
    GM_xmlhttpRequest({
        method: "GET",
        url: scriptURL,
        onload: function(response) {
            if (response.status === 200) {
                const html = response.responseText;
                const versionMatch = html.match(/@version\s+([\d.]+)/);
                if (versionMatch && versionMatch[1]) {
                    const latestVersion = versionMatch[1];
                    console.log("Latest version found:", latestVersion);
                    if (compareVersions(latestVersion, currentVersion) > 0) {
                        const message = `New Version: ${latestVersion} has been uploaded. Would you like me to take you there or continue with old version ${currentVersion}? (Not recommended for stability)`;
                        if (confirm(message)) {
                            window.location.href = scriptURL;
                        } else {
                            console.log("User chose to continue with old version.");
                        }
                    } else {
                        console.log("No newer version available.");
                    }
                }
            }
        },
    });
}

function compareVersions(v1, v2) {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);
    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
        const p1 = parts1[i] || 0;
        const p2 = parts2[i] || 0;
        if (p1 > p2) return 1;
        if (p1 < p2) return -1;
    }
    return 0;
}

function main() {
    let myVars = document.myVars = {
        autoMove: false,
        autoRun: false,
        autoMatch: false,
        delay: 0.1,
        gameEnded: false
    };
    let myFunctions = document.myFunctions = {};
    const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";
    let board;

    myFunctions.rescan = function() {
        if (board && board.game && typeof board.game.getFEN === 'function') {
            const fen = board.game.getFEN();
            console.log("Using built-in FEN:", fen);
            return fen;
        }
        console.warn("Could not get FEN from board.game object.");
        return null;
    };

    myFunctions.color = function(dat) {
        const bestmoveUCI = dat.split(' ')[1];
        console.log("Extracted bestmove UCI:", bestmoveUCI);
        if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
        else myFunctions.highlightMove(bestmoveUCI);
        isThinking = false;
        myFunctions.spinner();
    };

    myFunctions.highlightMove = function(bestmoveUCI) {
        const res1 = bestmoveUCI.substring(0, 2);
        const res2 = bestmoveUCI.substring(2, 4);
        $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`).delay(1800).queue(function() { $(this).remove(); });
        $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`).delay(1800).queue(function() { $(this).remove(); });
    };

    myFunctions.movePiece = function(bestmoveUCI) {
        if (!board || !board.game) { console.error("Board or board.game not initialized!"); return; }
        const fromSquare = bestmoveUCI.substring(0, 2);
        const toSquare = bestmoveUCI.substring(2, 4);
        const promotionPiece = bestmoveUCI.length > 4 ? bestmoveUCI.substring(4, 5) : null;
        const legalMoves = board.game.getLegalMoves();
        const foundMove = legalMoves.find(move =>
            move.from === fromSquare && move.to === toSquare && (promotionPiece ? move.promotion === promotionPiece : !move.promotion)
        );

        if (foundMove) {
            console.log("Executing move:", bestmoveUCI);
            board.game.move({ ...foundMove, animate: true, userGenerated: true });
        } else {
            console.warn(`Could not find a legal move for UCI: ${bestmoveUCI}.`);
        }
    };

    myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
        const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
        console.log(`Fetching from: ${apiURL}`);
        GM_xmlhttpRequest({
            method: "GET", url: apiURL,
            onload: function(response) {
                if (response.status === 200) {
                    try { const jsonResponse = JSON.parse(response.responseText);
                        if (jsonResponse.success) myFunctions.color(jsonResponse.bestmove);
                        else { console.error("API failed:", jsonResponse); isThinking = false; myFunctions.spinner(); }
                    } catch (e) { console.error("API parse error:", e); isThinking = false; myFunctions.spinner(); }
                } else { console.error("API error:", response.status); isThinking = false; myFunctions.spinner(); }
            },
            onerror: function() { console.error("API request error"); isThinking = false; myFunctions.spinner(); }
        });
    };

    myFunctions.startNewGame = function() {
        console.log("Starting new game...");
        const newGameButton = $('.game-over-modal-content .game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])') ||
                              $('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
        if (newGameButton.length) {
            newGameButton[0].click();
        } else {
            console.error("Could not find a 'New Game' button.");
        }
    };

    myFunctions.declineRematch = function() {
        const declineButton = $('.cc-button-component.cc-button-secondary[aria-label="Decline"], .cc-button-component.cc-button-secondary:contains("Decline")');
        if (declineButton.length) {
            declineButton[0].click();
            console.log("Declined rematch.");
        }
    };

    let lastValue = 12, MAX_DEPTH = 15, MIN_DEPTH = 1;

    myFunctions.runChessEngine = function(depth) {
        depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
        const fen = myFunctions.rescan();
        if (!fen) { console.log("Skipping analysis, FEN not available."); return; }
        console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
        isThinking = true;
        myFunctions.spinner();
        myFunctions.fetchBestMoveFromAPI(fen, depth);
        lastValue = depth;
        updateDepthDisplay();
    };

    function updateDepthDisplay() {
        if (uiElementsLoaded && $('#depthText')[0]) {
            $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
        }
    }

    myFunctions.incrementDepth = function(delta) {
        lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
        updateDepthDisplay();
    };

    myFunctions.autoRun = function() {
        if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
            myFunctions.runChessEngine(lastValue);
        }
    };

    document.onkeydown = function(e) {
        if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
        switch (e.keyCode) {
            case 81: myFunctions.runChessEngine(1); break; case 87: myFunctions.runChessEngine(2); break;
            case 69: myFunctions.runChessEngine(3); break; case 82: myFunctions.runChessEngine(4); break;
            case 84: myFunctions.runChessEngine(5); break; case 89: myFunctions.runChessEngine(6); break;
            case 85: myFunctions.runChessEngine(7); break; case 73: myFunctions.runChessEngine(8); break;
            case 79: myFunctions.runChessEngine(9); break; case 80: myFunctions.runChessEngine(10); break;
            case 65: myFunctions.runChessEngine(11); break; case 83: myFunctions.runChessEngine(12); break;
            case 68: myFunctions.runChessEngine(13); break; case 70: myFunctions.runChessEngine(14); break;
            case 71: myFunctions.runChessEngine(15); break; case 187: myFunctions.incrementDepth(1); break;
            case 189: myFunctions.incrementDepth(-1); break;
        }
    };

    myFunctions.spinner = function() {
        if (uiElementsLoaded && $('#overlay')[0]) {
            $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
        }
    };

    let uiElementsLoaded = false;
    let loaded = false;
    myFunctions.loadEx = function() {
        try {
            console.log("Attempting to load UI...");
            board = document.querySelector('chess-board, wc-chess-board');
            const style = document.createElement('style');
            style.innerHTML = `
                .chess-ui-panel { width: 280px; background: linear-gradient(135deg, #ffe6e6, #fff5f5); border: 2px solid #ffcccc; border-radius: 12px; box-shadow: 4px 4px 16px rgba(0,0,0,0.3); animation: fadeIn 1s ease-out; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; user-select: none; }
                .chess-ui-header { background: #ffcccc; border-bottom: 1px solid #ffb3b3; border-top-left-radius: 10px; border-top-right-radius: 10px; padding: 8px; cursor: move; font-weight: bold; color: #800000; text-align: center; }
                .chess-ui-body { padding: 10px; }
                .chess-ui-panel button { background: #e91e63; border: none; color: white; padding: 8px 12px; margin: 4px 2px; border-radius: 5px; cursor: pointer; transition: box-shadow 0.3s ease, transform 0.2s ease; }
                .chess-ui-panel button:hover { box-shadow: 0 0 8px #ff69b4; transform: scale(1.05); }
                .chess-ui-panel input[type="checkbox"], .chess-ui-panel input[type="number"] { margin: 5px 0; padding: 4px; border: 1px solid #ffcccc; border-radius: 4px; }
                @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
                body.is-dragging { user-select: none; }
            `;
            document.head.appendChild(style);

            const panel = document.createElement('div');
            panel.className = "chess-ui-panel";
            panel.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 10000;';
            panel.innerHTML = `
                <div class="chess-ui-header">Stockfish Bot (v${currentVersion})</div>
                <div class="chess-ui-body">
                    <p id="depthText">Depth: <strong>${lastValue}</strong></p>
                    <button id="depthMinus">-</button> <button id="depthPlus">+</button>
                    <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
                    <p id="engineVersionText">Engine: <strong>Stockfish API</strong> 😘</p>
                    <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
                    <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
                    <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
                    <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
                    <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="0.3" step="0.1" style="width: 60px;"></label><br>
                </div>`;
            document.body.appendChild(panel);

            const header = panel.querySelector('.chess-ui-header');
            makeDraggable(panel, header);

            setTimeout(() => {
                $('#depthPlus').on('click', () => myFunctions.incrementDepth(1));
                $('#depthMinus').on('click', () => myFunctions.incrementDepth(-1));
                $('#autoMatch').on('change', () => { myVars.autoMatch = $('#autoMatch')[0].checked; });
            }, 100);

            const spinCont = document.createElement('div');
            spinCont.id = 'overlay';
            spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
            panel.appendChild(spinCont);
            const spinr = document.createElement('div');
            spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
            spinCont.appendChild(spinr);
            const dynamicStyles = document.createElement('style');
            document.head.appendChild(dynamicStyles);
            dynamicStyles.sheet.insertRule('@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }', 0);

            loaded = true;
            uiElementsLoaded = true;
            checkForUpdate();
        } catch (error) { console.error("loadEx error:", error); }
    };

    function makeDraggable(panel, header) { let oX, oY; header.addEventListener('mousedown', e => { e.preventDefault(); const r = panel.getBoundingClientRect(); oX = e.clientX - r.left; oY = e.clientY - r.top; document.body.classList.add('is-dragging'); document.addEventListener('mousemove', elementDrag); document.addEventListener('mouseup', closeDragElement); }); function elementDrag(e) { e.preventDefault(); panel.style.left = (e.clientX - oX) + "px"; panel.style.top = (e.clientY - oY) + "px"; } function closeDragElement() { document.body.classList.remove('is-dragging'); document.removeEventListener('mousemove', elementDrag); document.removeEventListener('mouseup', closeDragElement); } }

    function runWithDelay(delay) {
        const endTime = Date.now() + delay;
        const timer = setInterval(() => {
            if (Date.now() >= endTime) {
                myFunctions.autoRun();
                canGo = true;
                clearInterval(timer);
            }
        }, 10);
    }

    const mainLoop = setInterval(() => {
        if (!loaded) {
            myFunctions.loadEx();
        } else {
            if (!board) board = document.querySelector('chess-board, wc-chess-board');
            if (!board || !board.game) return;

            myVars.autoRun = $('#autoRun')[0].checked;
            myVars.autoMove = $('#autoMove')[0].checked;
            myVars.autoMatch = $('#autoMatch')[0].checked;
            const minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
            const maxDel = parseFloat($('#timeDelayMax')[0].value) || 0.3;
            myVars.delay = Math.random() * (maxDel - minDel) + minDel;
            myTurn = board.game.getTurn() === board.game.getPlayingAs();
            updateDepthDisplay();

            const gameOver = document.querySelector('.game-over-message-component, .game-result');

            if (gameOver && !myVars.gameEnded) {
                console.log("Game ended detected.");
                myVars.gameEnded = true;
                if (myVars.autoMatch) {
                    myFunctions.declineRematch();
                    setTimeout(myFunctions.startNewGame, 1000);
                }
            } else if (!gameOver && myVars.gameEnded) {
                myVars.gameEnded = false;
            }

            // This is the simplified, correct logic.
            // It will trigger on any turn, including the first move (where getMoves().length is 0).
            if (myVars.autoRun && canGo && !isThinking && myTurn) {
                canGo = false;
                runWithDelay(myVars.delay * 1000);
            }
        }
    }, 200);

    setTimeout(() => { if (!loaded) myFunctions.loadEx(); }, 2000);
}

let isThinking = false, canGo = true, myTurn = false;
window.addEventListener("load", () => main());